From 48a284db55529c7170c14dd69cf3ed402cfe61a2 Mon Sep 17 00:00:00 2001 From: yuanjiajia <1139393632@qq.com> Date: Fri, 18 Nov 2022 15:28:46 +0800 Subject: [PATCH] yuanjiajia --- composer.json | 19 + composer.lock | 1575 +++++ console/index.php | 9 + qlOut/QueryListLog.txt | 1 + qlOut/errLog.txt | 1623 +++++ qlOut/recruit_info.xls | 295 + src/YuanjiajiaQl.php | 178 + vendor/autoload.php | 12 + vendor/bin/var-dump-server | 117 + .../adapter-common/AbstractCachePool.php | 559 ++ vendor/cache/adapter-common/CacheItem.php | 269 + vendor/cache/adapter-common/Changelog.md | 73 + .../Exception/CacheException.php | 23 + .../Exception/CachePoolException.php | 21 + .../Exception/InvalidArgumentException.php | 19 + .../HasExpirationTimestampInterface.php | 26 + .../adapter-common/JsonBinaryArmoring.php | 68 + vendor/cache/adapter-common/LICENSE | 22 + vendor/cache/adapter-common/PhpCacheItem.php | 32 + vendor/cache/adapter-common/PhpCachePool.php | 34 + vendor/cache/adapter-common/README.md | 15 + .../adapter-common/TagSupportWithArray.php | 88 + vendor/cache/adapter-common/composer.json | 55 + vendor/cache/filesystem-adapter/Changelog.md | 64 + .../FilesystemCachePool.php | 213 + vendor/cache/filesystem-adapter/LICENSE | 22 + vendor/cache/filesystem-adapter/README.md | 45 + vendor/cache/filesystem-adapter/composer.json | 55 + .../.github/PULL_REQUEST_TEMPLATE.md | 5 + vendor/cache/tag-interop/.gitignore | 2 + vendor/cache/tag-interop/.travis.yml | 22 + vendor/cache/tag-interop/Changelog.md | 18 + vendor/cache/tag-interop/LICENSE | 22 + vendor/cache/tag-interop/README.md | 25 + .../TaggableCacheItemInterface.php | 43 + .../TaggableCacheItemPoolInterface.php | 60 + vendor/cache/tag-interop/composer.json | 39 + vendor/composer/ClassLoader.php | 572 ++ vendor/composer/InstalledVersions.php | 352 + vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 28 + vendor/composer/autoload_files.php | 18 + vendor/composer/autoload_namespaces.php | 9 + vendor/composer/autoload_psr4.php | 29 + vendor/composer/autoload_real.php | 57 + vendor/composer/autoload_static.php | 186 + vendor/composer/installed.json | 1629 +++++ vendor/composer/installed.php | 260 + vendor/composer/platform_check.php | 26 + vendor/guzzlehttp/guzzle/CHANGELOG.md | 1519 ++++ vendor/guzzlehttp/guzzle/LICENSE | 27 + vendor/guzzlehttp/guzzle/README.md | 94 + vendor/guzzlehttp/guzzle/UPGRADING.md | 1253 ++++ vendor/guzzlehttp/guzzle/composer.json | 105 + .../guzzlehttp/guzzle/src/BodySummarizer.php | 28 + .../guzzle/src/BodySummarizerInterface.php | 13 + vendor/guzzlehttp/guzzle/src/Client.php | 477 ++ .../guzzlehttp/guzzle/src/ClientInterface.php | 84 + vendor/guzzlehttp/guzzle/src/ClientTrait.php | 241 + .../guzzle/src/Cookie/CookieJar.php | 317 + .../guzzle/src/Cookie/CookieJarInterface.php | 79 + .../guzzle/src/Cookie/FileCookieJar.php | 101 + .../guzzle/src/Cookie/SessionCookieJar.php | 77 + .../guzzle/src/Cookie/SetCookie.php | 446 ++ .../src/Exception/BadResponseException.php | 39 + .../guzzle/src/Exception/ClientException.php | 10 + .../guzzle/src/Exception/ConnectException.php | 56 + .../guzzle/src/Exception/GuzzleException.php | 9 + .../Exception/InvalidArgumentException.php | 7 + .../guzzle/src/Exception/RequestException.php | 166 + .../guzzle/src/Exception/ServerException.php | 10 + .../Exception/TooManyRedirectsException.php | 7 + .../src/Exception/TransferException.php | 7 + .../guzzle/src/Handler/CurlFactory.php | 595 ++ .../src/Handler/CurlFactoryInterface.php | 25 + .../guzzle/src/Handler/CurlHandler.php | 49 + .../guzzle/src/Handler/CurlMultiHandler.php | 262 + .../guzzle/src/Handler/EasyHandle.php | 112 + .../guzzle/src/Handler/HeaderProcessor.php | 42 + .../guzzle/src/Handler/MockHandler.php | 211 + .../guzzlehttp/guzzle/src/Handler/Proxy.php | 51 + .../guzzle/src/Handler/StreamHandler.php | 593 ++ vendor/guzzlehttp/guzzle/src/HandlerStack.php | 275 + .../guzzle/src/MessageFormatter.php | 198 + .../guzzle/src/MessageFormatterInterface.php | 18 + vendor/guzzlehttp/guzzle/src/Middleware.php | 260 + vendor/guzzlehttp/guzzle/src/Pool.php | 125 + .../guzzle/src/PrepareBodyMiddleware.php | 104 + .../guzzle/src/RedirectMiddleware.php | 228 + .../guzzlehttp/guzzle/src/RequestOptions.php | 264 + .../guzzlehttp/guzzle/src/RetryMiddleware.php | 116 + .../guzzlehttp/guzzle/src/TransferStats.php | 133 + vendor/guzzlehttp/guzzle/src/Utils.php | 385 ++ vendor/guzzlehttp/guzzle/src/functions.php | 167 + .../guzzle/src/functions_include.php | 6 + vendor/guzzlehttp/promises/CHANGELOG.md | 110 + vendor/guzzlehttp/promises/LICENSE | 24 + vendor/guzzlehttp/promises/README.md | 546 ++ vendor/guzzlehttp/promises/composer.json | 58 + .../promises/src/AggregateException.php | 17 + .../promises/src/CancellationException.php | 10 + vendor/guzzlehttp/promises/src/Coroutine.php | 169 + vendor/guzzlehttp/promises/src/Create.php | 84 + vendor/guzzlehttp/promises/src/Each.php | 90 + .../guzzlehttp/promises/src/EachPromise.php | 247 + .../promises/src/FulfilledPromise.php | 84 + vendor/guzzlehttp/promises/src/Is.php | 46 + vendor/guzzlehttp/promises/src/Promise.php | 278 + .../promises/src/PromiseInterface.php | 97 + .../promises/src/PromisorInterface.php | 16 + .../promises/src/RejectedPromise.php | 91 + .../promises/src/RejectionException.php | 48 + vendor/guzzlehttp/promises/src/TaskQueue.php | 67 + .../promises/src/TaskQueueInterface.php | 24 + vendor/guzzlehttp/promises/src/Utils.php | 276 + vendor/guzzlehttp/promises/src/functions.php | 363 + .../promises/src/functions_include.php | 6 + vendor/guzzlehttp/psr7/CHANGELOG.md | 396 ++ vendor/guzzlehttp/psr7/LICENSE | 26 + vendor/guzzlehttp/psr7/README.md | 872 +++ vendor/guzzlehttp/psr7/composer.json | 96 + vendor/guzzlehttp/psr7/src/AppendStream.php | 248 + vendor/guzzlehttp/psr7/src/BufferStream.php | 149 + vendor/guzzlehttp/psr7/src/CachingStream.php | 153 + vendor/guzzlehttp/psr7/src/DroppingStream.php | 49 + .../src/Exception/MalformedUriException.php | 14 + vendor/guzzlehttp/psr7/src/FnStream.php | 180 + vendor/guzzlehttp/psr7/src/Header.php | 134 + vendor/guzzlehttp/psr7/src/HttpFactory.php | 100 + vendor/guzzlehttp/psr7/src/InflateStream.php | 37 + vendor/guzzlehttp/psr7/src/LazyOpenStream.php | 41 + vendor/guzzlehttp/psr7/src/LimitStream.php | 157 + vendor/guzzlehttp/psr7/src/Message.php | 246 + vendor/guzzlehttp/psr7/src/MessageTrait.php | 264 + vendor/guzzlehttp/psr7/src/MimeType.php | 1237 ++++ .../guzzlehttp/psr7/src/MultipartStream.php | 159 + vendor/guzzlehttp/psr7/src/NoSeekStream.php | 28 + vendor/guzzlehttp/psr7/src/PumpStream.php | 179 + vendor/guzzlehttp/psr7/src/Query.php | 113 + vendor/guzzlehttp/psr7/src/Request.php | 157 + vendor/guzzlehttp/psr7/src/Response.php | 160 + vendor/guzzlehttp/psr7/src/Rfc7230.php | 23 + vendor/guzzlehttp/psr7/src/ServerRequest.php | 344 + vendor/guzzlehttp/psr7/src/Stream.php | 282 + .../psr7/src/StreamDecoratorTrait.php | 155 + vendor/guzzlehttp/psr7/src/StreamWrapper.php | 175 + vendor/guzzlehttp/psr7/src/UploadedFile.php | 211 + vendor/guzzlehttp/psr7/src/Uri.php | 740 ++ vendor/guzzlehttp/psr7/src/UriComparator.php | 52 + vendor/guzzlehttp/psr7/src/UriNormalizer.php | 220 + vendor/guzzlehttp/psr7/src/UriResolver.php | 211 + vendor/guzzlehttp/psr7/src/Utils.php | 459 ++ vendor/jaeger/g-http/.gitignore | 3 + vendor/jaeger/g-http/README.md | 158 + vendor/jaeger/g-http/composer.json | 20 + .../jaeger/g-http/examples/multi_request.php | 27 + .../g-http/examples/multi_request_2.php | 37 + .../g-http/examples/request_with_cache.php | 33 + vendor/jaeger/g-http/examples/simple.php | 22 + vendor/jaeger/g-http/src/Cache.php | 65 + vendor/jaeger/g-http/src/GHttp.php | 164 + .../g-http/src/MethodNotFoundException.php | 19 + vendor/jaeger/g-http/src/MultiRequest.php | 113 + vendor/jaeger/phpquery-single/README.md | 36 + vendor/jaeger/phpquery-single/composer.json | 24 + vendor/jaeger/phpquery-single/phpQuery.php | 6086 +++++++++++++++++ vendor/jaeger/querylist/.github/FUNDING.yml | 12 + vendor/jaeger/querylist/.gitignore | 5 + vendor/jaeger/querylist/README-ZH.md | 309 + vendor/jaeger/querylist/README.md | 304 + vendor/jaeger/querylist/composer.json | 40 + vendor/jaeger/querylist/logo.png | Bin 0 -> 15609 bytes vendor/jaeger/querylist/phpunit.xml | 19 + vendor/jaeger/querylist/src/Config.php | 94 + .../src/Contracts/PluginContract.php | 15 + .../src/Contracts/ServiceProviderContract.php | 15 + vendor/jaeger/querylist/src/Dom/Dom.php | 30 + vendor/jaeger/querylist/src/Dom/Elements.php | 260 + vendor/jaeger/querylist/src/Dom/Query.php | 322 + .../Exceptions/ServiceNotFoundException.php | 15 + vendor/jaeger/querylist/src/Kernel.php | 74 + .../src/Providers/EncodeServiceProvider.php | 22 + .../src/Providers/HttpServiceProvider.php | 40 + .../src/Providers/PluginServiceProvider.php | 23 + .../src/Providers/SystemServiceProvider.php | 32 + vendor/jaeger/querylist/src/QueryList.php | 133 + .../querylist/src/Services/EncodeService.php | 37 + .../querylist/src/Services/HttpService.php | 59 + .../src/Services/MultiRequestService.php | 66 + .../querylist/src/Services/PluginService.php | 26 + .../jaeger/querylist/tests/Dom/FindTest.php | 71 + .../jaeger/querylist/tests/Dom/RulesTest.php | 43 + .../querylist/tests/Feature/HttpTest.php | 103 + .../querylist/tests/Feature/InstanceTest.php | 48 + .../querylist/tests/Feature/MethodTest.php | 36 + .../jaeger/querylist/tests/TestCaseBase.php | 20 + .../querylist/tests/assets/snippet-1.html | 9 + .../querylist/tests/assets/snippet-2.html | 16 + vendor/jaeger/querylist/tests/bootstrap.php | 5 + vendor/league/flysystem/CODE_OF_CONDUCT.md | 76 + vendor/league/flysystem/LICENSE | 19 + vendor/league/flysystem/SECURITY.md | 16 + vendor/league/flysystem/composer.json | 68 + vendor/league/flysystem/deprecations.md | 19 + .../flysystem/src/Adapter/AbstractAdapter.php | 72 + .../src/Adapter/AbstractFtpAdapter.php | 705 ++ .../src/Adapter/CanOverwriteFiles.php | 12 + vendor/league/flysystem/src/Adapter/Ftp.php | 584 ++ vendor/league/flysystem/src/Adapter/Ftpd.php | 48 + vendor/league/flysystem/src/Adapter/Local.php | 533 ++ .../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 + .../src/ConnectionErrorException.php | 9 + .../src/ConnectionRuntimeException.php | 9 + .../flysystem/src/CorruptedPathDetected.php | 17 + 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 | 409 ++ .../flysystem/src/FilesystemException.php | 7 + .../flysystem/src/FilesystemInterface.php | 284 + .../src/FilesystemNotFoundException.php | 12 + vendor/league/flysystem/src/Handler.php | 137 + .../flysystem/src/InvalidRootException.php | 9 + 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 | 354 + .../src/Util/ContentListingFormatter.php | 122 + vendor/league/flysystem/src/Util/MimeType.php | 80 + .../flysystem/src/Util/StreamHasher.php | 36 + .../league/mime-type-detection/CHANGELOG.md | 31 + vendor/league/mime-type-detection/LICENSE | 19 + .../league/mime-type-detection/composer.json | 34 + .../src/EmptyExtensionToMimeTypeMap.php | 13 + .../src/ExtensionMimeTypeDetector.php | 42 + .../src/ExtensionToMimeTypeMap.php | 10 + .../src/FinfoMimeTypeDetector.php | 92 + .../src/GeneratedExtensionToMimeTypeMap.php | 1227 ++++ .../src/MimeTypeDetector.php | 19 + .../src/OverridingExtensionToMimeTypeMap.php | 30 + 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/http-client/CHANGELOG.md | 23 + vendor/psr/http-client/LICENSE | 19 + vendor/psr/http-client/README.md | 12 + vendor/psr/http-client/composer.json | 27 + .../src/ClientExceptionInterface.php | 10 + .../psr/http-client/src/ClientInterface.php | 20 + .../src/NetworkExceptionInterface.php | 24 + .../src/RequestExceptionInterface.php | 24 + vendor/psr/http-factory/.gitignore | 2 + vendor/psr/http-factory/.pullapprove.yml | 7 + vendor/psr/http-factory/LICENSE | 21 + vendor/psr/http-factory/README.md | 10 + vendor/psr/http-factory/composer.json | 35 + .../src/RequestFactoryInterface.php | 18 + .../src/ResponseFactoryInterface.php | 18 + .../src/ServerRequestFactoryInterface.php | 24 + .../src/StreamFactoryInterface.php | 45 + .../src/UploadedFileFactoryInterface.php | 34 + .../http-factory/src/UriFactoryInterface.php | 17 + vendor/psr/http-message/CHANGELOG.md | 36 + vendor/psr/http-message/LICENSE | 19 + vendor/psr/http-message/README.md | 13 + vendor/psr/http-message/composer.json | 26 + .../psr/http-message/src/MessageInterface.php | 187 + .../psr/http-message/src/RequestInterface.php | 129 + .../http-message/src/ResponseInterface.php | 68 + .../src/ServerRequestInterface.php | 261 + .../psr/http-message/src/StreamInterface.php | 158 + .../src/UploadedFileInterface.php | 123 + vendor/psr/http-message/src/UriInterface.php | 323 + vendor/psr/log/LICENSE | 19 + vendor/psr/log/Psr/Log/AbstractLogger.php | 128 + .../log/Psr/Log/InvalidArgumentException.php | 7 + vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + vendor/psr/log/Psr/Log/LoggerAwareTrait.php | 26 + vendor/psr/log/Psr/Log/LoggerInterface.php | 125 + vendor/psr/log/Psr/Log/LoggerTrait.php | 142 + vendor/psr/log/Psr/Log/NullLogger.php | 30 + vendor/psr/log/Psr/Log/Test/DummyTest.php | 18 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 138 + vendor/psr/log/Psr/Log/Test/TestLogger.php | 147 + vendor/psr/log/README.md | 58 + vendor/psr/log/composer.json | 26 + vendor/psr/simple-cache/.editorconfig | 12 + vendor/psr/simple-cache/LICENSE.md | 21 + vendor/psr/simple-cache/README.md | 8 + vendor/psr/simple-cache/composer.json | 25 + .../psr/simple-cache/src/CacheException.php | 10 + .../psr/simple-cache/src/CacheInterface.php | 114 + .../src/InvalidArgumentException.php | 13 + vendor/ralouphie/getallheaders/LICENSE | 21 + vendor/ralouphie/getallheaders/README.md | 27 + vendor/ralouphie/getallheaders/composer.json | 26 + .../getallheaders/src/getallheaders.php | 46 + .../symfony/deprecation-contracts/.gitignore | 3 + .../deprecation-contracts/CHANGELOG.md | 5 + vendor/symfony/deprecation-contracts/LICENSE | 19 + .../symfony/deprecation-contracts/README.md | 26 + .../deprecation-contracts/composer.json | 35 + .../deprecation-contracts/function.php | 27 + vendor/symfony/polyfill-mbstring/LICENSE | 19 + vendor/symfony/polyfill-mbstring/Mbstring.php | 874 +++ vendor/symfony/polyfill-mbstring/README.md | 13 + .../Resources/unidata/lowerCase.php | 1397 ++++ .../Resources/unidata/titleCaseRegexp.php | 5 + .../Resources/unidata/upperCase.php | 1489 ++++ .../symfony/polyfill-mbstring/bootstrap.php | 147 + .../symfony/polyfill-mbstring/bootstrap80.php | 143 + .../symfony/polyfill-mbstring/composer.json | 41 + vendor/symfony/polyfill-php80/LICENSE | 19 + vendor/symfony/polyfill-php80/Php80.php | 115 + vendor/symfony/polyfill-php80/PhpToken.php | 103 + vendor/symfony/polyfill-php80/README.md | 25 + .../Resources/stubs/Attribute.php | 31 + .../Resources/stubs/PhpToken.php | 16 + .../Resources/stubs/Stringable.php | 20 + .../Resources/stubs/UnhandledMatchError.php | 16 + .../Resources/stubs/ValueError.php | 16 + vendor/symfony/polyfill-php80/bootstrap.php | 42 + vendor/symfony/polyfill-php80/composer.json | 40 + vendor/symfony/var-dumper/CHANGELOG.md | 72 + .../symfony/var-dumper/Caster/AmqpCaster.php | 212 + vendor/symfony/var-dumper/Caster/ArgsStub.php | 80 + vendor/symfony/var-dumper/Caster/Caster.php | 170 + .../symfony/var-dumper/Caster/ClassStub.php | 106 + .../symfony/var-dumper/Caster/ConstStub.php | 36 + .../var-dumper/Caster/CutArrayStub.php | 30 + vendor/symfony/var-dumper/Caster/CutStub.php | 64 + .../symfony/var-dumper/Caster/DOMCaster.php | 304 + .../symfony/var-dumper/Caster/DateCaster.php | 127 + .../var-dumper/Caster/DoctrineCaster.php | 62 + vendor/symfony/var-dumper/Caster/DsCaster.php | 70 + .../symfony/var-dumper/Caster/DsPairStub.php | 28 + vendor/symfony/var-dumper/Caster/EnumStub.php | 30 + .../var-dumper/Caster/ExceptionCaster.php | 388 ++ .../symfony/var-dumper/Caster/FiberCaster.php | 43 + .../symfony/var-dumper/Caster/FrameStub.php | 30 + .../symfony/var-dumper/Caster/GmpCaster.php | 32 + .../var-dumper/Caster/ImagineCaster.php | 37 + vendor/symfony/var-dumper/Caster/ImgStub.php | 26 + .../symfony/var-dumper/Caster/IntlCaster.php | 172 + vendor/symfony/var-dumper/Caster/LinkStub.php | 108 + .../var-dumper/Caster/MemcachedCaster.php | 81 + .../var-dumper/Caster/MysqliCaster.php | 33 + .../symfony/var-dumper/Caster/PdoCaster.php | 122 + .../symfony/var-dumper/Caster/PgSqlCaster.php | 156 + .../var-dumper/Caster/ProxyManagerCaster.php | 33 + .../var-dumper/Caster/RdKafkaCaster.php | 186 + .../symfony/var-dumper/Caster/RedisCaster.php | 152 + .../var-dumper/Caster/ReflectionCaster.php | 442 ++ .../var-dumper/Caster/ResourceCaster.php | 103 + .../symfony/var-dumper/Caster/SplCaster.php | 245 + .../symfony/var-dumper/Caster/StubCaster.php | 84 + .../var-dumper/Caster/SymfonyCaster.php | 97 + .../symfony/var-dumper/Caster/TraceStub.php | 36 + .../symfony/var-dumper/Caster/UuidCaster.php | 30 + .../var-dumper/Caster/XmlReaderCaster.php | 91 + .../var-dumper/Caster/XmlResourceCaster.php | 63 + .../var-dumper/Cloner/AbstractCloner.php | 400 ++ .../var-dumper/Cloner/ClonerInterface.php | 27 + vendor/symfony/var-dumper/Cloner/Cursor.php | 43 + vendor/symfony/var-dumper/Cloner/Data.php | 468 ++ .../var-dumper/Cloner/DumperInterface.php | 56 + vendor/symfony/var-dumper/Cloner/Stub.php | 67 + .../symfony/var-dumper/Cloner/VarCloner.php | 311 + .../Command/Descriptor/CliDescriptor.php | 79 + .../Descriptor/DumpDescriptorInterface.php | 23 + .../Command/Descriptor/HtmlDescriptor.php | 119 + .../var-dumper/Command/ServerDumpCommand.php | 114 + .../var-dumper/Dumper/AbstractDumper.php | 202 + .../symfony/var-dumper/Dumper/CliDumper.php | 652 ++ .../ContextProvider/CliContextProvider.php | 32 + .../ContextProviderInterface.php | 22 + .../RequestContextProvider.php | 51 + .../ContextProvider/SourceContextProvider.php | 126 + .../Dumper/ContextualizedDumper.php | 43 + .../var-dumper/Dumper/DataDumperInterface.php | 24 + .../symfony/var-dumper/Dumper/HtmlDumper.php | 986 +++ .../var-dumper/Dumper/ServerDumper.php | 53 + .../Exception/ThrowingCasterException.php | 26 + vendor/symfony/var-dumper/LICENSE | 19 + vendor/symfony/var-dumper/README.md | 15 + .../var-dumper/Resources/bin/var-dump-server | 67 + .../Resources/css/htmlDescriptor.css | 130 + .../var-dumper/Resources/functions/dump.php | 50 + .../var-dumper/Resources/js/htmlDescriptor.js | 10 + .../symfony/var-dumper/Server/Connection.php | 99 + .../symfony/var-dumper/Server/DumpServer.php | 115 + .../var-dumper/Test/VarDumperTestTrait.php | 84 + vendor/symfony/var-dumper/VarDumper.php | 115 + vendor/symfony/var-dumper/composer.json | 50 + .../collect/.github/workflows/run-tests.yml | 41 + .../tightenco/collect/branch-commit-push.sh | 67 + vendor/tightenco/collect/composer.json | 51 + vendor/tightenco/collect/framework-.zip | 0 .../Collect/Contracts/Support/Arrayable.php | 13 + .../Support/CanBeEscapedWhenCastToString.php | 14 + .../Collect/Contracts/Support/Htmlable.php | 13 + .../Collect/Contracts/Support/Jsonable.php | 14 + .../collect/src/Collect/Support/Arr.php | 747 ++ .../src/Collect/Support/Collection.php | 1672 +++++ .../src/Collect/Support/Enumerable.php | 1027 +++ .../Support/HigherOrderCollectionProxy.php | 63 + .../Collect/Support/HigherOrderWhenProxy.php | 63 + .../src/Collect/Support/LazyCollection.php | 1585 +++++ .../Support/Traits/EnumeratesValues.php | 1116 +++ .../src/Collect/Support/Traits/Macroable.php | 126 + .../src/Collect/Support/Traits/Tappable.php | 17 + .../collect/src/Collect/Support/alias.php | 24 + .../collect/src/Collect/Support/helpers.php | 122 + 444 files changed, 69275 insertions(+) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 console/index.php create mode 100644 qlOut/QueryListLog.txt create mode 100644 qlOut/errLog.txt create mode 100644 qlOut/recruit_info.xls create mode 100644 src/YuanjiajiaQl.php create mode 100644 vendor/autoload.php create mode 100755 vendor/bin/var-dump-server create mode 100644 vendor/cache/adapter-common/AbstractCachePool.php create mode 100644 vendor/cache/adapter-common/CacheItem.php create mode 100644 vendor/cache/adapter-common/Changelog.md create mode 100644 vendor/cache/adapter-common/Exception/CacheException.php create mode 100644 vendor/cache/adapter-common/Exception/CachePoolException.php create mode 100644 vendor/cache/adapter-common/Exception/InvalidArgumentException.php create mode 100644 vendor/cache/adapter-common/HasExpirationTimestampInterface.php create mode 100644 vendor/cache/adapter-common/JsonBinaryArmoring.php create mode 100644 vendor/cache/adapter-common/LICENSE create mode 100644 vendor/cache/adapter-common/PhpCacheItem.php create mode 100644 vendor/cache/adapter-common/PhpCachePool.php create mode 100644 vendor/cache/adapter-common/README.md create mode 100644 vendor/cache/adapter-common/TagSupportWithArray.php create mode 100644 vendor/cache/adapter-common/composer.json create mode 100644 vendor/cache/filesystem-adapter/Changelog.md create mode 100644 vendor/cache/filesystem-adapter/FilesystemCachePool.php create mode 100644 vendor/cache/filesystem-adapter/LICENSE create mode 100644 vendor/cache/filesystem-adapter/README.md create mode 100644 vendor/cache/filesystem-adapter/composer.json create mode 100644 vendor/cache/tag-interop/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 vendor/cache/tag-interop/.gitignore create mode 100644 vendor/cache/tag-interop/.travis.yml create mode 100644 vendor/cache/tag-interop/Changelog.md create mode 100644 vendor/cache/tag-interop/LICENSE create mode 100644 vendor/cache/tag-interop/README.md create mode 100644 vendor/cache/tag-interop/TaggableCacheItemInterface.php create mode 100644 vendor/cache/tag-interop/TaggableCacheItemPoolInterface.php create mode 100644 vendor/cache/tag-interop/composer.json create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/InstalledVersions.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/composer/installed.php create mode 100644 vendor/composer/platform_check.php create mode 100644 vendor/guzzlehttp/guzzle/CHANGELOG.md 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/BodySummarizer.php create mode 100644 vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php 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/ClientTrait.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/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/HeaderProcessor.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/MessageFormatterInterface.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/Utils.php create mode 100644 vendor/guzzlehttp/guzzle/src/functions.php create mode 100644 vendor/guzzlehttp/guzzle/src/functions_include.php create mode 100644 vendor/guzzlehttp/promises/CHANGELOG.md create mode 100644 vendor/guzzlehttp/promises/LICENSE create mode 100644 vendor/guzzlehttp/promises/README.md create mode 100644 vendor/guzzlehttp/promises/composer.json create mode 100644 vendor/guzzlehttp/promises/src/AggregateException.php create mode 100644 vendor/guzzlehttp/promises/src/CancellationException.php create mode 100644 vendor/guzzlehttp/promises/src/Coroutine.php create mode 100644 vendor/guzzlehttp/promises/src/Create.php create mode 100644 vendor/guzzlehttp/promises/src/Each.php create mode 100644 vendor/guzzlehttp/promises/src/EachPromise.php create mode 100644 vendor/guzzlehttp/promises/src/FulfilledPromise.php create mode 100644 vendor/guzzlehttp/promises/src/Is.php create mode 100644 vendor/guzzlehttp/promises/src/Promise.php create mode 100644 vendor/guzzlehttp/promises/src/PromiseInterface.php create mode 100644 vendor/guzzlehttp/promises/src/PromisorInterface.php create mode 100644 vendor/guzzlehttp/promises/src/RejectedPromise.php create mode 100644 vendor/guzzlehttp/promises/src/RejectionException.php create mode 100644 vendor/guzzlehttp/promises/src/TaskQueue.php create mode 100644 vendor/guzzlehttp/promises/src/TaskQueueInterface.php create mode 100644 vendor/guzzlehttp/promises/src/Utils.php create mode 100644 vendor/guzzlehttp/promises/src/functions.php create mode 100644 vendor/guzzlehttp/promises/src/functions_include.php create mode 100644 vendor/guzzlehttp/psr7/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/Exception/MalformedUriException.php create mode 100644 vendor/guzzlehttp/psr7/src/FnStream.php create mode 100644 vendor/guzzlehttp/psr7/src/Header.php create mode 100644 vendor/guzzlehttp/psr7/src/HttpFactory.php create mode 100644 vendor/guzzlehttp/psr7/src/InflateStream.php create mode 100644 vendor/guzzlehttp/psr7/src/LazyOpenStream.php create mode 100644 vendor/guzzlehttp/psr7/src/LimitStream.php create mode 100644 vendor/guzzlehttp/psr7/src/Message.php create mode 100644 vendor/guzzlehttp/psr7/src/MessageTrait.php create mode 100644 vendor/guzzlehttp/psr7/src/MimeType.php create mode 100644 vendor/guzzlehttp/psr7/src/MultipartStream.php create mode 100644 vendor/guzzlehttp/psr7/src/NoSeekStream.php create mode 100644 vendor/guzzlehttp/psr7/src/PumpStream.php create mode 100644 vendor/guzzlehttp/psr7/src/Query.php create mode 100644 vendor/guzzlehttp/psr7/src/Request.php create mode 100644 vendor/guzzlehttp/psr7/src/Response.php create mode 100644 vendor/guzzlehttp/psr7/src/Rfc7230.php create mode 100644 vendor/guzzlehttp/psr7/src/ServerRequest.php create mode 100644 vendor/guzzlehttp/psr7/src/Stream.php create mode 100644 vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php create mode 100644 vendor/guzzlehttp/psr7/src/StreamWrapper.php create mode 100644 vendor/guzzlehttp/psr7/src/UploadedFile.php create mode 100644 vendor/guzzlehttp/psr7/src/Uri.php create mode 100644 vendor/guzzlehttp/psr7/src/UriComparator.php create mode 100644 vendor/guzzlehttp/psr7/src/UriNormalizer.php create mode 100644 vendor/guzzlehttp/psr7/src/UriResolver.php create mode 100644 vendor/guzzlehttp/psr7/src/Utils.php create mode 100644 vendor/jaeger/g-http/.gitignore create mode 100644 vendor/jaeger/g-http/README.md create mode 100644 vendor/jaeger/g-http/composer.json create mode 100644 vendor/jaeger/g-http/examples/multi_request.php create mode 100644 vendor/jaeger/g-http/examples/multi_request_2.php create mode 100644 vendor/jaeger/g-http/examples/request_with_cache.php create mode 100644 vendor/jaeger/g-http/examples/simple.php create mode 100644 vendor/jaeger/g-http/src/Cache.php create mode 100644 vendor/jaeger/g-http/src/GHttp.php create mode 100644 vendor/jaeger/g-http/src/MethodNotFoundException.php create mode 100644 vendor/jaeger/g-http/src/MultiRequest.php create mode 100644 vendor/jaeger/phpquery-single/README.md create mode 100644 vendor/jaeger/phpquery-single/composer.json create mode 100644 vendor/jaeger/phpquery-single/phpQuery.php create mode 100644 vendor/jaeger/querylist/.github/FUNDING.yml create mode 100644 vendor/jaeger/querylist/.gitignore create mode 100644 vendor/jaeger/querylist/README-ZH.md create mode 100644 vendor/jaeger/querylist/README.md create mode 100644 vendor/jaeger/querylist/composer.json create mode 100644 vendor/jaeger/querylist/logo.png create mode 100644 vendor/jaeger/querylist/phpunit.xml create mode 100644 vendor/jaeger/querylist/src/Config.php create mode 100644 vendor/jaeger/querylist/src/Contracts/PluginContract.php create mode 100644 vendor/jaeger/querylist/src/Contracts/ServiceProviderContract.php create mode 100644 vendor/jaeger/querylist/src/Dom/Dom.php create mode 100644 vendor/jaeger/querylist/src/Dom/Elements.php create mode 100644 vendor/jaeger/querylist/src/Dom/Query.php create mode 100644 vendor/jaeger/querylist/src/Exceptions/ServiceNotFoundException.php create mode 100644 vendor/jaeger/querylist/src/Kernel.php create mode 100644 vendor/jaeger/querylist/src/Providers/EncodeServiceProvider.php create mode 100644 vendor/jaeger/querylist/src/Providers/HttpServiceProvider.php create mode 100644 vendor/jaeger/querylist/src/Providers/PluginServiceProvider.php create mode 100644 vendor/jaeger/querylist/src/Providers/SystemServiceProvider.php create mode 100644 vendor/jaeger/querylist/src/QueryList.php create mode 100644 vendor/jaeger/querylist/src/Services/EncodeService.php create mode 100644 vendor/jaeger/querylist/src/Services/HttpService.php create mode 100644 vendor/jaeger/querylist/src/Services/MultiRequestService.php create mode 100644 vendor/jaeger/querylist/src/Services/PluginService.php create mode 100644 vendor/jaeger/querylist/tests/Dom/FindTest.php create mode 100644 vendor/jaeger/querylist/tests/Dom/RulesTest.php create mode 100644 vendor/jaeger/querylist/tests/Feature/HttpTest.php create mode 100644 vendor/jaeger/querylist/tests/Feature/InstanceTest.php create mode 100644 vendor/jaeger/querylist/tests/Feature/MethodTest.php create mode 100644 vendor/jaeger/querylist/tests/TestCaseBase.php create mode 100644 vendor/jaeger/querylist/tests/assets/snippet-1.html create mode 100644 vendor/jaeger/querylist/tests/assets/snippet-2.html create mode 100644 vendor/jaeger/querylist/tests/bootstrap.php create mode 100644 vendor/league/flysystem/CODE_OF_CONDUCT.md create mode 100644 vendor/league/flysystem/LICENSE create mode 100644 vendor/league/flysystem/SECURITY.md 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/ConnectionErrorException.php create mode 100644 vendor/league/flysystem/src/ConnectionRuntimeException.php create mode 100644 vendor/league/flysystem/src/CorruptedPathDetected.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/FilesystemException.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/InvalidRootException.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/league/mime-type-detection/CHANGELOG.md create mode 100644 vendor/league/mime-type-detection/LICENSE create mode 100644 vendor/league/mime-type-detection/composer.json create mode 100644 vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php create mode 100644 vendor/league/mime-type-detection/src/ExtensionMimeTypeDetector.php create mode 100644 vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php create mode 100644 vendor/league/mime-type-detection/src/FinfoMimeTypeDetector.php create mode 100644 vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php create mode 100644 vendor/league/mime-type-detection/src/MimeTypeDetector.php create mode 100644 vendor/league/mime-type-detection/src/OverridingExtensionToMimeTypeMap.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/http-client/CHANGELOG.md create mode 100644 vendor/psr/http-client/LICENSE create mode 100644 vendor/psr/http-client/README.md create mode 100644 vendor/psr/http-client/composer.json create mode 100644 vendor/psr/http-client/src/ClientExceptionInterface.php create mode 100644 vendor/psr/http-client/src/ClientInterface.php create mode 100644 vendor/psr/http-client/src/NetworkExceptionInterface.php create mode 100644 vendor/psr/http-client/src/RequestExceptionInterface.php create mode 100644 vendor/psr/http-factory/.gitignore create mode 100644 vendor/psr/http-factory/.pullapprove.yml create mode 100644 vendor/psr/http-factory/LICENSE create mode 100644 vendor/psr/http-factory/README.md create mode 100644 vendor/psr/http-factory/composer.json create mode 100644 vendor/psr/http-factory/src/RequestFactoryInterface.php create mode 100644 vendor/psr/http-factory/src/ResponseFactoryInterface.php create mode 100644 vendor/psr/http-factory/src/ServerRequestFactoryInterface.php create mode 100644 vendor/psr/http-factory/src/StreamFactoryInterface.php create mode 100644 vendor/psr/http-factory/src/UploadedFileFactoryInterface.php create mode 100644 vendor/psr/http-factory/src/UriFactoryInterface.php create mode 100644 vendor/psr/http-message/CHANGELOG.md create mode 100644 vendor/psr/http-message/LICENSE create mode 100644 vendor/psr/http-message/README.md create mode 100644 vendor/psr/http-message/composer.json create mode 100644 vendor/psr/http-message/src/MessageInterface.php create mode 100644 vendor/psr/http-message/src/RequestInterface.php create mode 100644 vendor/psr/http-message/src/ResponseInterface.php create mode 100644 vendor/psr/http-message/src/ServerRequestInterface.php create mode 100644 vendor/psr/http-message/src/StreamInterface.php create mode 100644 vendor/psr/http-message/src/UploadedFileInterface.php create mode 100644 vendor/psr/http-message/src/UriInterface.php create mode 100644 vendor/psr/log/LICENSE create mode 100644 vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 vendor/psr/log/Psr/Log/Test/DummyTest.php create mode 100644 vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 vendor/psr/log/Psr/Log/Test/TestLogger.php create mode 100644 vendor/psr/log/README.md create mode 100644 vendor/psr/log/composer.json create mode 100644 vendor/psr/simple-cache/.editorconfig create mode 100644 vendor/psr/simple-cache/LICENSE.md create mode 100644 vendor/psr/simple-cache/README.md create mode 100644 vendor/psr/simple-cache/composer.json create mode 100644 vendor/psr/simple-cache/src/CacheException.php create mode 100644 vendor/psr/simple-cache/src/CacheInterface.php create mode 100644 vendor/psr/simple-cache/src/InvalidArgumentException.php create mode 100644 vendor/ralouphie/getallheaders/LICENSE create mode 100644 vendor/ralouphie/getallheaders/README.md create mode 100644 vendor/ralouphie/getallheaders/composer.json create mode 100644 vendor/ralouphie/getallheaders/src/getallheaders.php create mode 100644 vendor/symfony/deprecation-contracts/.gitignore create mode 100644 vendor/symfony/deprecation-contracts/CHANGELOG.md create mode 100644 vendor/symfony/deprecation-contracts/LICENSE create mode 100644 vendor/symfony/deprecation-contracts/README.md create mode 100644 vendor/symfony/deprecation-contracts/composer.json create mode 100644 vendor/symfony/deprecation-contracts/function.php create mode 100644 vendor/symfony/polyfill-mbstring/LICENSE create mode 100644 vendor/symfony/polyfill-mbstring/Mbstring.php create mode 100644 vendor/symfony/polyfill-mbstring/README.md create mode 100644 vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php create mode 100644 vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 vendor/symfony/polyfill-mbstring/bootstrap.php create mode 100644 vendor/symfony/polyfill-mbstring/bootstrap80.php create mode 100644 vendor/symfony/polyfill-mbstring/composer.json create mode 100644 vendor/symfony/polyfill-php80/LICENSE create mode 100644 vendor/symfony/polyfill-php80/Php80.php create mode 100644 vendor/symfony/polyfill-php80/PhpToken.php create mode 100644 vendor/symfony/polyfill-php80/README.md create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php create mode 100644 vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php create mode 100644 vendor/symfony/polyfill-php80/bootstrap.php create mode 100644 vendor/symfony/polyfill-php80/composer.json create mode 100644 vendor/symfony/var-dumper/CHANGELOG.md create mode 100644 vendor/symfony/var-dumper/Caster/AmqpCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/ArgsStub.php create mode 100644 vendor/symfony/var-dumper/Caster/Caster.php create mode 100644 vendor/symfony/var-dumper/Caster/ClassStub.php create mode 100644 vendor/symfony/var-dumper/Caster/ConstStub.php create mode 100644 vendor/symfony/var-dumper/Caster/CutArrayStub.php create mode 100644 vendor/symfony/var-dumper/Caster/CutStub.php create mode 100644 vendor/symfony/var-dumper/Caster/DOMCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/DateCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/DoctrineCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/DsCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/DsPairStub.php create mode 100644 vendor/symfony/var-dumper/Caster/EnumStub.php create mode 100644 vendor/symfony/var-dumper/Caster/ExceptionCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/FiberCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/FrameStub.php create mode 100644 vendor/symfony/var-dumper/Caster/GmpCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/ImagineCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/ImgStub.php create mode 100644 vendor/symfony/var-dumper/Caster/IntlCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/LinkStub.php create mode 100644 vendor/symfony/var-dumper/Caster/MemcachedCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/MysqliCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/PdoCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/PgSqlCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/RdKafkaCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/RedisCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/ReflectionCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/ResourceCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/SplCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/StubCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/SymfonyCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/TraceStub.php create mode 100644 vendor/symfony/var-dumper/Caster/UuidCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/XmlReaderCaster.php create mode 100644 vendor/symfony/var-dumper/Caster/XmlResourceCaster.php create mode 100644 vendor/symfony/var-dumper/Cloner/AbstractCloner.php create mode 100644 vendor/symfony/var-dumper/Cloner/ClonerInterface.php create mode 100644 vendor/symfony/var-dumper/Cloner/Cursor.php create mode 100644 vendor/symfony/var-dumper/Cloner/Data.php create mode 100644 vendor/symfony/var-dumper/Cloner/DumperInterface.php create mode 100644 vendor/symfony/var-dumper/Cloner/Stub.php create mode 100644 vendor/symfony/var-dumper/Cloner/VarCloner.php create mode 100644 vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php create mode 100644 vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php create mode 100644 vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php create mode 100644 vendor/symfony/var-dumper/Command/ServerDumpCommand.php create mode 100644 vendor/symfony/var-dumper/Dumper/AbstractDumper.php create mode 100644 vendor/symfony/var-dumper/Dumper/CliDumper.php create mode 100644 vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php create mode 100644 vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php create mode 100644 vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php create mode 100644 vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php create mode 100644 vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php create mode 100644 vendor/symfony/var-dumper/Dumper/DataDumperInterface.php create mode 100644 vendor/symfony/var-dumper/Dumper/HtmlDumper.php create mode 100644 vendor/symfony/var-dumper/Dumper/ServerDumper.php create mode 100644 vendor/symfony/var-dumper/Exception/ThrowingCasterException.php create mode 100644 vendor/symfony/var-dumper/LICENSE create mode 100644 vendor/symfony/var-dumper/README.md create mode 100755 vendor/symfony/var-dumper/Resources/bin/var-dump-server create mode 100644 vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css create mode 100644 vendor/symfony/var-dumper/Resources/functions/dump.php create mode 100644 vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js create mode 100644 vendor/symfony/var-dumper/Server/Connection.php create mode 100644 vendor/symfony/var-dumper/Server/DumpServer.php create mode 100644 vendor/symfony/var-dumper/Test/VarDumperTestTrait.php create mode 100644 vendor/symfony/var-dumper/VarDumper.php create mode 100644 vendor/symfony/var-dumper/composer.json create mode 100644 vendor/tightenco/collect/.github/workflows/run-tests.yml create mode 100755 vendor/tightenco/collect/branch-commit-push.sh create mode 100644 vendor/tightenco/collect/composer.json create mode 100644 vendor/tightenco/collect/framework-.zip create mode 100755 vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php create mode 100644 vendor/tightenco/collect/src/Collect/Contracts/Support/CanBeEscapedWhenCastToString.php create mode 100644 vendor/tightenco/collect/src/Collect/Contracts/Support/Htmlable.php create mode 100755 vendor/tightenco/collect/src/Collect/Contracts/Support/Jsonable.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/Arr.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/Collection.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/Enumerable.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/HigherOrderWhenProxy.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/LazyCollection.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/Traits/Macroable.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/alias.php create mode 100644 vendor/tightenco/collect/src/Collect/Support/helpers.php diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..254288a --- /dev/null +++ b/composer.json @@ -0,0 +1,19 @@ +{ + "name": "yuanjiajia/qldome", + "type": "library", + "require": { + "php": ">=7.4,<8.0", + "jaeger/querylist": "^4.2" + }, + "autoload": { + "psr-4": { + "Yuanjiajia\\Qldome\\": "src/" + } + }, + "authors": [ + { + "name": "yuanjiajia", + "email": "1139393632@qq.com" + } + ] +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..241cc70 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1575 @@ +{ + "_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": "5e061580574ceee6d8cb775ec8c5091b", + "packages": [ + { + "name": "cache/adapter-common", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/adapter-common.git", + "reference": "8788309be72aa7be69b88cdc0687549c74a7d479" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/adapter-common/zipball/8788309be72aa7be69b88cdc0687549c74a7d479", + "reference": "8788309be72aa7be69b88cdc0687549c74a7d479", + "shasum": "" + }, + "require": { + "cache/tag-interop": "^1.0", + "php": ">=7.4", + "psr/cache": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "cache/integration-tests": "^0.17", + "phpunit/phpunit": "^7.5.20 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Common\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "description": "Common classes for PSR-6 adapters", + "homepage": "http://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "psr-6", + "tag" + ], + "support": { + "source": "https://github.com/php-cache/adapter-common/tree/1.3.0" + }, + "time": "2022-01-15T15:47:19+00:00" + }, + { + "name": "cache/filesystem-adapter", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/filesystem-adapter.git", + "reference": "f1faaae40aaa696ef899cef6f6888aedb90b419b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/filesystem-adapter/zipball/f1faaae40aaa696ef899cef6f6888aedb90b419b", + "reference": "f1faaae40aaa696ef899cef6f6888aedb90b419b", + "shasum": "" + }, + "require": { + "cache/adapter-common": "^1.0", + "league/flysystem": "^1.0", + "php": ">=7.4", + "psr/cache": "^1.0 || ^2.0", + "psr/simple-cache": "^1.0" + }, + "provide": { + "psr/cache-implementation": "^1.0", + "psr/simple-cache-implementation": "^1.0" + }, + "require-dev": { + "cache/integration-tests": "^0.17", + "phpunit/phpunit": "^7.5.20 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "description": "A PSR-6 cache implementation using filesystem. This implementation supports tags", + "homepage": "http://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "filesystem", + "psr-6", + "tag" + ], + "support": { + "source": "https://github.com/php-cache/filesystem-adapter/tree/1.2.0" + }, + "time": "2022-01-15T15:47:19+00:00" + }, + { + "name": "cache/tag-interop", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/tag-interop.git", + "reference": "b062b1d735357da50edf8387f7a8696f3027d328" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/tag-interop/zipball/b062b1d735357da50edf8387f7a8696f3027d328", + "reference": "b062b1d735357da50edf8387f7a8696f3027d328", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0 || ^8.0", + "psr/cache": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Cache\\TagInterop\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com", + "homepage": "https://github.com/nicolas-grekas" + } + ], + "description": "Framework interoperable interfaces for tags", + "homepage": "https://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "psr", + "psr6", + "tag" + ], + "support": { + "issues": "https://github.com/php-cache/tag-interop/issues", + "source": "https://github.com/php-cache/tag-interop/tree/1.1.0" + }, + "time": "2021-12-31T10:03:23+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-08-28T15:39:27+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "b94b2807d85443f9719887892882d0329d1e2598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2022-08-28T14:55:35+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "67c26b443f348a51926030c83481b85718457d3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", + "reference": "67c26b443f348a51926030c83481b85718457d3d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-10-26T14:07:24+00:00" + }, + { + "name": "jaeger/g-http", + "version": "V1.7.2", + "source": { + "type": "git", + "url": "https://github.com/jae-jae/GHttp.git", + "reference": "82585ddd5e2c6651e37ab1d8166efcdbb6b293d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jae-jae/GHttp/zipball/82585ddd5e2c6651e37ab1d8166efcdbb6b293d4", + "reference": "82585ddd5e2c6651e37ab1d8166efcdbb6b293d4", + "shasum": "" + }, + "require": { + "cache/filesystem-adapter": "^1", + "guzzlehttp/guzzle": "^6.0 | ^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jaeger\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaeger", + "email": "JaegerCode@gmail.com" + } + ], + "description": "Simple Http client base on GuzzleHttp", + "support": { + "issues": "https://github.com/jae-jae/GHttp/issues", + "source": "https://github.com/jae-jae/GHttp/tree/V1.7.2" + }, + "time": "2021-08-08T04:59:44+00:00" + }, + { + "name": "jaeger/phpquery-single", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/jae-jae/phpQuery-single.git", + "reference": "39a650ade692a6b480c22220dce0c198d6a946fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jae-jae/phpQuery-single/zipball/39a650ade692a6b480c22220dce0c198d6a946fb", + "reference": "39a650ade692a6b480c22220dce0c198d6a946fb", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "phpQuery.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobiasz Cudnik", + "email": "tobiasz.cudnik@gmail.com", + "homepage": "https://github.com/TobiaszCudnik", + "role": "Developer" + }, + { + "name": "Jaeger", + "role": "Packager" + } + ], + "description": "phpQuery单文件版本,是Querylist的依赖(http://querylist.cc/),phpQuery项目主页:http://code.google.com/p/phpquery/", + "homepage": "http://code.google.com/p/phpquery/", + "support": { + "issues": "https://github.com/jae-jae/phpQuery-single/issues", + "source": "https://github.com/jae-jae/phpQuery-single/tree/1.1.1" + }, + "time": "2022-03-26T15:01:16+00:00" + }, + { + "name": "jaeger/querylist", + "version": "V4.2.8", + "source": { + "type": "git", + "url": "https://github.com/jae-jae/QueryList.git", + "reference": "39dc0ca9c668bec7a793e20472ccd7d26ef89ea4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jae-jae/QueryList/zipball/39dc0ca9c668bec7a793e20472ccd7d26ef89ea4", + "reference": "39dc0ca9c668bec7a793e20472ccd7d26ef89ea4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "jaeger/g-http": "^1.1", + "jaeger/phpquery-single": "^1", + "php": ">=7.1", + "tightenco/collect": ">5.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5", + "symfony/var-dumper": "^3.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "QL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaeger", + "email": "JaegerCode@gmail.com" + } + ], + "description": "Simple, elegant, extensible PHP Web Scraper (crawler/spider),Use the css3 dom selector,Based on phpQuery! 简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。", + "homepage": "http://querylist.cc", + "keywords": [ + "QueryList", + "phpQuery", + "spider" + ], + "support": { + "issues": "https://github.com/jae-jae/QueryList/issues", + "source": "https://github.com/jae-jae/QueryList/tree/V4.2.8" + }, + "funding": [ + { + "url": "https://opencollective.com/querylist", + "type": "open_collective" + } + ], + "time": "2021-07-05T06:07:58+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.10", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "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" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.10" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2022-10-04T09:16:37+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2022-04-17T13:12:02+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" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.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 interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+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" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "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" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v5.4.14", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "6894d06145fefebd9a4c7272baa026a1c394a430" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6894d06145fefebd9a4c7272baa026a1c394a430", + "reference": "6894d06145fefebd9a4c7272baa026a1c394a430", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v5.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-07T08:01:20+00:00" + }, + { + "name": "tightenco/collect", + "version": "v8.83.25", + "source": { + "type": "git", + "url": "https://github.com/tighten/collect.git", + "reference": "7d2a6fc5e97c5f7209a780bea98f35042c1fd0ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tighten/collect/zipball/7d2a6fc5e97c5f7209a780bea98f35042c1fd0ea", + "reference": "7d2a6fc5e97c5f7209a780bea98f35042c1fd0ea", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0", + "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "nesbot/carbon": "^2.23.0", + "phpunit/phpunit": "^8.3" + }, + "type": "library", + "autoload": { + "files": [ + "src/Collect/Support/helpers.php", + "src/Collect/Support/alias.php" + ], + "psr-4": { + "Tightenco\\Collect\\": "src/Collect" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "Collect - Illuminate Collections as a separate package.", + "keywords": [ + "collection", + "laravel" + ], + "support": { + "issues": "https://github.com/tighten/collect/issues", + "source": "https://github.com/tighten/collect/tree/v8.83.25" + }, + "time": "2022-08-22T17:55:07+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.4,<8.0" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/console/index.php b/console/index.php new file mode 100644 index 0000000..5642949 --- /dev/null +++ b/console/index.php @@ -0,0 +1,9 @@ +actionIndex(); \ No newline at end of file diff --git a/qlOut/QueryListLog.txt b/qlOut/QueryListLog.txt new file mode 100644 index 0000000..8f92bfd --- /dev/null +++ b/qlOut/QueryListLog.txt @@ -0,0 +1 @@ +35 diff --git a/qlOut/errLog.txt b/qlOut/errLog.txt new file mode 100644 index 0000000..c5f552a --- /dev/null +++ b/qlOut/errLog.txt @@ -0,0 +1,1623 @@ +数据无效(2): 当前第 1 页: 第 6 个无效。与 Parsons Music Limited,Reyes Cheng,23331863,tutor@parsonsmusic.com,https://recruit.hkfew.org.hk/jobs/%e5%85%92%e7%ab%a5%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-children-music-class-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%99%a8%e6%a8%82%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-musical-instrument-tutor-part-time/ + + +数据无效(2): 当前第 1 页: 第 7 个无效。与 Parsons Music Limited,Reyes Cheng,23331863,tutor@parsonsmusic.com,https://recruit.hkfew.org.hk/jobs/%e5%85%92%e7%ab%a5%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-children-music-class-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%b5%81%e8%a1%8c%e9%bc%93%e5%b0%8e%e5%b8%ab-pop-drum-tutor/ + + +数据无效(2): 当前第 1 页: 第 8 个无效。与 Parsons Music Limited,Reyes Cheng,23331863,tutor@parsonsmusic.com,https://recruit.hkfew.org.hk/jobs/%e5%85%92%e7%ab%a5%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-children-music-class-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-piano-tutor/ + + +数据无效(2): 当前第 1 页: 第 10 个无效。与 聖雅各福群會 延續教育中心,Daphne Suen,59885027,daphne.suen@sjs.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e5%b0%88%e7%a7%91%e7%8f%ad%e8%8d%83%e7%81%a3%e3%80%81%e8%91%b5%e6%b6%8c%e5%8d%80-%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99%e5%b8%ab-6/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e4%ba%8c%e5%ad%b8%e7%bf%92%e8%aa%9e%e6%96%87%e6%8a%80%e5%b7%a7%e5%b0%8f%e7%b5%84%e9%9d%92%e8%a1%a3%e5%8d%80-2/ + + +数据无效(2): 当前第 1 页: 第 17 个无效。与 勵致研習中心,黃生,61125961,fair_ltd@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-secondary-school-and-primary-school-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e5%b0%8e%e5%b8%ab-dse-tutor/ + + +数据无效(1): 当前第 1 页: 第 20 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7%ef%bc%89/ + + +数据无效(2): 当前第 2 页: 第 24 个无效。与 Kinderland Playgroup and Learning Centre Limited,Miss kwan,95604448,kidskinderland@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e6%99%ae%e9%80%9a%e8%a9%b1%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/english-playgroup-teacher-2/ + + +数据无效(2): 当前第 2 页: 第 25 个无效。与 Kinderland Playgroup and Learning Centre Limited,Miss kwan,95604448,kidskinderland@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e6%99%ae%e9%80%9a%e8%a9%b1%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%95%ab%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 2 页: 第 29 个无效。与 ENGLISH EXPRESS EDUCATION CENTRE,Miss Lam,54265083,tutor.com.hk@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%8a%e9%96%80%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e6%99%ae%e9%80%9a%e8%a9%b1/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e5%85%bc%e8%81%b7%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e6%99%ae%e9%80%9a%e8%a9%b1-%e7%89%b9%e6%ae%8a%e6%95%99%e8%82%b2-%e6%95%99%e5%ad%b8%e8%bc%94/ + + +数据无效(2): 当前第 3 页: 第 47 个无效。与 Mood Education (Prince Edward),Ms Chan,84904568,moodventure2022@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-3/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 3 页: 第 50 个无效。与 大衆教室(田心村),Ms Li,66501338,populartaiwai@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-6/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-5/ + + +数据无效(2): 当前第 3 页: 第 51 个无效。与 HKTA香港導師會,李小姐,21142188,april.lee@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%a4%a9%e6%b0%b4%e5%9c%8d%e5%8d%80%e4%b8%ad%e5%ad%b8%e4%b8%ad%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%84%e5%8d%80%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab%e7%a9%ba%e7%bc%ba/ + + +数据无效(2): 当前第 3 页: 第 55 个无效。与 奇趣學藝坊有限公司,薜小姐,25200353,info.school.hkclp@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%87%e8%bb%8d%e6%be%b3%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%b7%b1%e6%b0%b4%e5%9f%97%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 3 页: 第 56 个无效。与 奇趣學藝坊有限公司,薜小姐,25200353,info.school.hkclp@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%87%e8%bb%8d%e6%be%b3%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%b2%99%e7%94%b0-%e5%b0%8f%e5%ad%b8%e4%b8%ad%e3%80%81%e8%8b%b1%e6%96%87%e5%ad%b8%e7%bf%92%e7%8f%ad-%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 3 页: 第 57 个无效。与 奇趣學藝坊有限公司,薜小姐,25200353,info.school.hkclp@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%87%e8%bb%8d%e6%be%b3%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%ef%bc%88%e6%b2%99%e7%94%b0-%e8%8b%b1%e6%96%87%e5%ad%b8%e7%bf%92%e7%8f%ad-%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 3 页: 第 60 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e6%b8%af%e5%8d%81%e5%85%ab%e5%8d%80%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab%e4%b8%ad%e8%8b%b1%e6%95%b8sen%e9%9d%9e%e8%8f%af%e8%aa%9e%e7%8f%ad%e5%8f%af/ + + +数据无效(2): 当前第 4 页: 第 61 个无效。与 大衆教室佐敦分校(恒豐中心),李小姐,94162842,sinotrend13@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-8/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-7/ + + +数据无效(2): 当前第 4 页: 第 62 个无效。与 小領袖潛能發展教育中心,楊小姐,29479998,new.wonders@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%88%e7%b4%84%e5%88%b0%e6%a0%a1%ef%bc%88%e5%88%9d%e4%b8%ad%ef%bc%89%e5%a5%a7%e6%95%b8%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%b0%8f%e5%ad%b8%e6%a0%a1%e6%9c%ac%e8%aa%b2%e7%a8%8b%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 4 页: 第 65 个无效。与 香港教育服務中心,Erica Cheng,39924299,hr@hkescedu.com,https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e6%9d%b1%e6%b6%8c%e5%8d%80%e4%bb%a5%e9%9d%a2%e6%8e%88%e5%bd%a2%e5%bc%8f%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%80%a5%e8%81%98%e5%9c%a8%e5%ae%b6%e7%b6%b2%e8%aa%b2%e5%88%b0%e6%a0%a1%e6%9d%8f%e8%8a%b1%e9%82%a8-%e6%b2%99%e7%94%b0%e5%8d%80-%e8%a7%80%e5%a1%98%e5%8d%80-%e9%b4%a8%e8%84%b7%e6%b4%b2%e5%8d%80/ + + +数据无效(2): 当前第 4 页: 第 66 个无效。与 東華三院邱金元中學,楊先生 / 吳小姐,26497385,recruit@twyky.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e8%aa%9e%e6%96%87%e8%83%bd%e5%8a%9b%e7%b2%be%e9%80%b2%e7%8f%ad/ + + +数据无效(2): 当前第 4 页: 第 68 个无效。与 聖雅各福群會 延續教育中心,Daphne Suen,59885027,daphne.suen@sjs.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e5%b0%88%e7%a7%91%e7%8f%ad%e8%8d%83%e7%81%a3%e3%80%81%e8%91%b5%e6%b6%8c%e5%8d%80-%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99%e5%b8%ab-6/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e4%ba%8c%e5%ad%b8%e7%bf%92%e8%aa%9e%e6%96%87%e6%8a%80%e5%b7%a7%e5%b0%8f%e7%b5%84%e9%9d%92%e8%a1%a3%e5%8d%80/ + + +数据无效(2): 当前第 4 页: 第 69 个无效。与 香港教育服務中心,Erica Cheng,39924299,hr@hkescedu.com,https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e6%9d%b1%e6%b6%8c%e5%8d%80%e4%bb%a5%e9%9d%a2%e6%8e%88%e5%bd%a2%e5%bc%8f%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%84%e5%8d%80%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%88%b0%e6%a0%a1%e9%9d%a2%e6%8e%88%e8%88%88%e8%b6%a3%e7%8f%ad-%e5%8a%9f%e8%aa%b2%e7%8f%ad-%e5%b0%88%e7%a7%91%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 4 页: 第 78 个无效。与 孔聖堂中學,莫玉玲副校長,25763415,enquiry@chss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%80%a5%e8%81%98%e9%9d%9e%e8%8f%af%e8%aa%9e%e4%b8%ad%e6%96%87%e8%aa%b2%e5%be%8c%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-3/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%80%a5%e8%81%98%e9%9d%9e%e8%8f%af%e8%aa%9e%e4%b8%ad%e6%96%87%e8%aa%b2%e5%be%8c%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 4 页: 第 80 个无效。与 大衆教室(田心村),Ms Li,66501338,populartaiwai@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-6/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-4/ + + +数据无效(2): 当前第 5 页: 第 81 个无效。与 Mood Education (Prince Edward),Ms Chan,84904568,moodventure2022@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-3/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 5 页: 第 82 个无效。与 孔聖堂中學,莫玉玲副校長,25763415,enquiry@chss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%80%a5%e8%81%98%e9%9d%9e%e8%8f%af%e8%aa%9e%e4%b8%ad%e6%96%87%e8%aa%b2%e5%be%8c%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-3/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%80%a5%e8%81%98%e9%9d%9e%e8%8f%af%e8%aa%9e%e4%b8%ad%e6%96%87%e8%aa%b2%e5%be%8c%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 5 页: 第 83 个无效。与 瑪利諾神父教會學校,傅先生,27775117,mfshr@mfs2.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e6%9c%a8%e7%b5%90%e4%bb%96%ef%bc%89/ + + +数据无效(2): 当前第 5 页: 第 85 个无效。与 文理書院(香港),校務處,25567413,cognitiohk@cognitiohk.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9b%9b%e7%b4%9a%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%a0%e8%81%982022-2023%e5%b9%b4%e5%ba%a6%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 5 页: 第 87 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%ab%8b%e5%88%b0%e6%a0%a1%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%e5%bd%a9%e8%99%b9%e8%91%b5%e6%b6%8c%e6%b2%99%e7%94%b0%e8%a7%80%e5%a1%98%e5%a0%85%e5%b0%bc%e5%9c%b0%e9%81%93/ + + +数据无效(2): 当前第 5 页: 第 93 个无效。与 Enlightening Education Limited,Ms Cheung,91414028,recruit@enlighteningedu.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%8d%8a%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e6%95%99-%e5%b0%87%e8%bb%8d%e6%be%b3-playgroup-teacher-teaching-assistant-tseung-kwan-o/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%8d%8a%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e6%95%99-%e8%a5%bf%e7%87%9f%e7%9b%a4-playgroup-teacher-teaching-assistant-sai-ying-pun/ + + +数据无效(2): 当前第 5 页: 第 94 个无效。与 Enlightening Education Limited,Ms Cheung,91414028,recruit@enlighteningedu.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%8d%8a%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e6%95%99-%e5%b0%87%e8%bb%8d%e6%be%b3-playgroup-teacher-teaching-assistant-tseung-kwan-o/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e6%95%99%e8%8d%83%e7%81%a3-playgroup-teacher-teaching-assistant-tww/ + + +数据无效(2): 当前第 5 页: 第 95 个无效。与 Enlightening Education Limited,Ms Cheung,91414028,recruit@enlighteningedu.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%8d%8a%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e6%95%99-%e5%b0%87%e8%bb%8d%e6%be%b3-playgroup-teacher-teaching-assistant-tseung-kwan-o/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e6%95%99%e6%9d%8f%e8%8a%b1%e9%82%a8-playgroup-teacher-teaching-assistant-%e6%9d%8f%e8%8a%b1%e9%82%a8/ + + +数据无效(2): 当前第 5 页: 第 96 个无效。与 Enlightening Education Limited,Ms Cheung,91414028,recruit@enlighteningedu.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%8d%8a%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e6%95%99-%e5%b0%87%e8%bb%8d%e6%be%b3-playgroup-teacher-teaching-assistant-tseung-kwan-o/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%8d%8a%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e6%95%99-%e8%8d%83%e7%81%a3-playgroup-teacher-teaching-assistant-tseun-wan-2/ + + +数据无效(2): 当前第 5 页: 第 98 个无效。与 東華三院邱金元中學,楊先生 / 吳小姐,26497385,recruit@twyky.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 6 页: 第 101 个无效。与 MAMAGREENIA嬰幼教育園,馬小姐,51106339,info@mamagreenia.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e7%be%8e%e8%a1%93%e5%b0%8e%e5%b8%ab%e5%9b%9b%e5%a4%a9%e5%b7%a5%e4%bd%9c/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%be%8e%e8%a1%93%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 6 页: 第 102 个无效。与 MAMAGREENIA嬰幼教育園,馬小姐,51106339,info@mamagreenia.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e7%be%8e%e8%a1%93%e5%b0%8e%e5%b8%ab%e5%9b%9b%e5%a4%a9%e5%b7%a5%e4%bd%9c/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%80%b1%e6%9c%ab%e5%85%bc%e8%81%b7%e7%be%8e%e8%a1%93%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 6 页: 第 104 个无效。与 勵致研習中心,黃生,24622576,fair_ltd@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-5-6%e5%a4%a9-%e8%a7%80%e5%a1%98%e5%ae%89%e9%81%94-secondary-school-and-primary-school-tutor-5-6days-on-tat-estate-kwun-tong/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab5-6%e5%a4%a9-%e8%a7%80%e5%a1%98%e5%ae%89%e9%81%94/ + + +数据无效(2): 当前第 6 页: 第 106 个无效。与 大衆教室佐敦分校(恒豐中心),李小姐,94162842,sinotrend13@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-8/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-6/ + + +数据无效(2): 当前第 6 页: 第 107 个无效。与 HKCCCU Logos Academy,Personnel Department,23372123,jobs@logosacademy.edu.hk,https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e8%aa%9e%e6%96%87%e7%a7%91%e8%aa%b2%e5%be%8c%e5%a2%9e%e6%bd%a4%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 6 页: 第 109 个无效。与 幸福文化,阮小姐,23370678,recruit@likogroup.com.hk,https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 6 页: 第 110 个无效。与 華英中學,行政主任麥先生,27607772,recruit@waying.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/international-mathematical-olympiad-tutor/ + + +数据无效(2): 当前第 6 页: 第 111 个无效。与 大衆教室(田心村),Ms Li,66501338,populartaiwai@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-6/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-3/ + + +数据无效(2): 当前第 6 页: 第 115 个无效。与 聖雅各福群會 延續教育中心,Daphne Suen,59885027,daphne.suen@sjs.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e5%b0%88%e7%a7%91%e7%8f%ad%e8%8d%83%e7%81%a3%e3%80%81%e8%91%b5%e6%b6%8c%e5%8d%80-%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99%e5%b8%ab-6/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e3%80%8e%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e3%80%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d-%e4%b8%80%e8%87%b3%e4%ba%94-%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 7 页: 第 122 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a9%e5%90%8d%ef%bc%89-2/ + + +数据无效(2): 当前第 7 页: 第 130 个无效。与 大衆教室佐敦分校(恒豐中心),李小姐,94162842,sinotrend13@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-8/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-5/ + + +数据无效(2): 当前第 7 页: 第 135 个无效。与 明愛賽馬會黃大仙綜合服務,周姑娘,23820265,chaukm.joyce@caritassws.org.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 7 页: 第 138 个无效。与 EDUKEY CENTRE FOR EDUCATION,Miss Chan,27940851,info.epc.edu@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b9%9d%e9%be%8d%e5%9f%8e/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8e%e5%b8%ab%ef%bc%9a%e5%85%a8%e8%81%b7/ + + +数据无效(2): 当前第 7 页: 第 139 个无效。与 圓玄學院妙法寺內明陳呂重德紀念中學,魏小姐,24458899,admin@clctmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e6%8b%94%e5%b0%96%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e6%8b%94%e5%b0%96%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 8 页: 第 142 个无效。与 明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e9%9b%bb%e7%ab%b6%ef%bc%88e-sports%ef%bc%89%ef%bc%89/ + + +数据无效(2): 当前第 8 页: 第 143 个无效。与 明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%91%9c%e7%8f%88%e7%8f%ad%ef%bc%89/ + + +数据无效(2): 当前第 8 页: 第 144 个无效。与 明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e9%8b%bc%e7%90%b4%e7%8f%ad%ef%bc%89/ + + +数据无效(2): 当前第 8 页: 第 145 个无效。与 明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e8%97%9d%e8%a1%93%e5%b0%8f%e7%b5%84%ef%bc%89/ + + +数据无效(2): 当前第 8 页: 第 147 个无效。与 Tom Lee Music Co. Ltd,Ophelia Choi,27377530,hr@tomleemusic.com,https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-13/ + + +数据无效(2): 当前第 8 页: 第 150 个无效。与 數研喜樂教育中心,陳先生,36197929,blissful@soinedu.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e6%95%b8%e5%ad%b8%e5%b0%8e%e5%b8%ab-maths-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e6%95%b8%e5%ad%b8%e5%b0%8e%e5%b8%ab-mathematics-tutor/ + + +数据无效(2): 当前第 8 页: 第 151 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a4%a7%e9%87%8f%e6%8b%9b%e8%81%98%e7%b7%9a%e4%b8%8aonline%e7%b7%9a%e4%b8%8a%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab%e4%b8%ad%e5%ad%b8%e5%b0%8f%e5%ad%b8%e4%b8%ad%e8%8b%b1%e6%95%b8%e5%8a%9f%e8%bc%94/ + + +数据无效(2): 当前第 8 页: 第 153 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a9%e5%90%8d%ef%bc%89/ + + +数据无效(2): 当前第 9 页: 第 165 个无效。与 賽馬會萬鈞毅智書院(政府津貼文法中學),黃小姐,24472322,eduyoung@jcmkec.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-11/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%85%bc%e8%81%b7%e8%a3%9c%e8%aa%b2%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 9 页: 第 167 个无效。与 康盈中英文幼稚園,盧承依校長,27723163,hongyinginfo@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-15/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-14/ + + +数据无效(2): 当前第 9 页: 第 168 个无效。与 大衆教室佐敦分校(恒豐中心),李小姐,94162842,sinotrend13@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-8/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e8%87%b3%e5%88%9d%e4%b8%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 9 页: 第 169 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%a4%a7%e5%ad%b8%e7%95%a2%e6%a5%ad%e5%b0%8e%e5%b8%ab%e5%88%b0%e6%a0%a1%e6%99%82%e8%96%aa180%e4%bb%a5%e4%b8%8a%e4%b8%ad%e6%96%87%e8%8b%b1%e6%96%87%e6%95%b8%e5%ad%b8%e5%b0%8e/ + + +数据无效(2): 当前第 9 页: 第 170 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e7%b7%9a%e4%b8%8aonline%e5%b0%8e%e5%b8%ab%e5%9c%a8%e5%ae%b6%e5%b7%a5%e4%bd%9c%e5%8a%9f%e8%bc%94%e7%8f%ad%e3%80%81%e6%95%b8%e5%ad%b8%e3%80%81%e9%9d%a2%e8%a9%a6%e3%80%81%e7%a4%be/ + + +数据无效(2): 当前第 9 页: 第 171 个无效。与 爾雅教育,何小姐,56012170,eryaeducation2021@gmail.com,https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e4%b8%ad%e5%bf%83%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e4%b8%ad%e5%bf%83%e6%95%b8%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-4/ + + +数据无效(2): 当前第 9 页: 第 173 个无效。与 爾雅教育,何小姐,56012170,eryaeducation2021@gmail.com,https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e4%b8%ad%e5%bf%83%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e4%b8%ad%e5%bf%83%e6%95%b8%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ + + +数据无效(2): 当前第 9 页: 第 174 个无效。与 Tom Lee Music Co. Ltd,Ophelia Choi,27377530,hr@tomleemusic.com,https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%bc%93%e5%8f%8a%e6%95%b2%e6%93%8a%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-drum-percussion-tutor/ + + +数据无效(2): 当前第 9 页: 第 175 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e6%98%9f%e6%9c%9f%e5%85%ad%e6%95%b8%e5%ad%b8%e5%b0%8e%e5%b8%ab-%e5%88%b0%e6%a0%a1/ + + +数据无效(2): 当前第 9 页: 第 177 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%b7%e5%a4%a7%e5%b0%88%e7%94%9f%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab%e4%b8%ad%e8%8b%b1%e6%95%b8%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 9 页: 第 178 个无效。与 基督教香港信義會元朗信義中學,陳貴月小姐 (秘書),24480622,tswyllss@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9f%93%e5%9c%8b%e6%96%87%e5%8c%96%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 10 页: 第 181 个无效。与 Tom Lee Music Co. Ltd,Ophelia Choi,27377530,hr@tomleemusic.com,https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e6%b5%81%e8%a1%8c%e9%bc%93%e8%80%81%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 10 页: 第 183 个无效。与 中華基督教會馮梁結紀念中學,區小姐,26516033,info@flk.edu.hk,https://recruit.hkfew.org.hk/jobs/e-%e6%a8%82%e5%9c%98%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%88%e5%94%b1%e5%9c%98%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 10 页: 第 188 个无效。与 賽馬會萬鈞毅智書院(政府津貼文法中學),黃小姐,24472322,eduyoung@jcmkec.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-11/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%85%bc%e8%81%b7%e8%a3%9c%e8%aa%b2%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 10 页: 第 189 个无效。与 賽馬會萬鈞毅智書院(政府津貼文法中學),黃小姐,24472322,eduyoung@jcmkec.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-11/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 10 页: 第 190 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%a5%bf%e6%b4%8b%e6%95%b2%e6%93%8a%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab-3/ + + +数据无效(2): 当前第 10 页: 第 191 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a4%a7%e6%8f%90%e7%90%b4%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab-3/ + + +数据无效(2): 当前第 10 页: 第 192 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%85%ad%e6%97%85%e9%81%8a%e8%88%87%e6%ac%be%e5%be%85%e7%a7%91%e8%aa%b2%e5%be%8c%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 10 页: 第 194 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%a5%bf%e6%b4%8b%e6%95%b2%e6%93%8a%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 10 页: 第 195 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a4%a7%e6%8f%90%e7%90%b4%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 10 页: 第 196 个无效。与 明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%ad%a5%e6%93%8d%e5%8f%8a%e5%8d%87%e6%97%97%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 10 页: 第 197 个无效。与 明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%a6%96%e8%a6%ba%e8%97%9d%e8%a1%93%e7%a7%91%e8%b3%87%e5%84%aa%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 11 页: 第 202 个无效。与 大衆教室佐敦分校(恒豐中心),李小姐,94162842,sinotrend13@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-8/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-4/ + + +数据无效(2): 当前第 11 页: 第 204 个无效。与 聖士提反女子中學,Ms. Josephine Yuen,25492521,ssgcpost@ssgc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/english-enhancement-class-instructor-part-time/ + + +数据无效(2): 当前第 11 页: 第 205 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 11 页: 第 211 个无效。与 聖士提反女子中學,Ms. Josephine Yuen,25492521,ssgcpost@ssgc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/part%e2%80%90time-music-instructors-and-coaches/ + + +数据无效(2): 当前第 11 页: 第 212 个无效。与 Dunn's Education 梓峰教育,許小姐,98535867,recruit.dunn@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%b7%a5%e4%bd%9c%e5%9c%b0%e9%bb%9e%ef%bc%9a%e4%b9%9d%e9%be%8d%e7%81%a3/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%b7%a5%e4%bd%9c%e5%9c%b0%e9%bb%9e%ef%bc%9a%e4%b9%9d%e9%be%8d%e7%81%a3%ef%bc%89/ + + +数据无效(2): 当前第 11 页: 第 213 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b9%92%e4%b9%93%e7%90%83%e6%95%99%e7%b7%b4-5/ + + +数据无效(2): 当前第 11 页: 第 214 个无效。与 華英中學,行政主任麥先生,27607772,recruit@waying.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%b7%86%e6%8b%b3%e9%81%93%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 11 页: 第 215 个无效。与 HKCCCU Logos Academy,Personnel Department,23372123,jobs@logosacademy.edu.hk,https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/f-2-f-3-english-tutor/ + + +数据无效(2): 当前第 11 页: 第 216 个无效。与 東華三院蕭旺李滿福幼兒園,李小姐,25802273,swlmfns@tungwah.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-19/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-18/ + + +数据无效(2): 当前第 11 页: 第 217 个无效。与 Mood Education (Prince Edward),Ms Chan,84904568,moodventure2022@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-3/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%8d%8a%e8%81%b7%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 11 页: 第 219 个无效。与 荃灣kidversity,Miss Lam,31690588,kidsbobo111@gmail.com,https://recruit.hkfew.org.hk/jobs/playgroup-%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e7%90%86%e4%b8%ad%e5%bf%83%e4%b8%bb%e7%ae%a1/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/playgroup-teacher-centre-assistant-manager/ + + +数据无效(2): 当前第 11 页: 第 220 个无效。与 HKCCCU Logos Academy,Personnel Department,23372123,jobs@logosacademy.edu.hk,https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/harp-instructor/ + + +数据无效(2): 当前第 12 页: 第 226 个无效。与 Man Kai Education,Miss Chan,26688294,eunice@monkeymaster.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%a8%e8%81%b7%e5%b0%8e%e5%b8%ab-full-time-tutor-5/ + + +数据无效(2): 当前第 12 页: 第 231 个无效。与 浸信會沙田教育中心,陳小姐,82003351,baptistec@hkstbc.org,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%85%bc%e8%81%b7-%e6%b2%99%e7%94%b0%e5%8d%80/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab%e5%85%bc%e8%81%b7-%e6%b2%99%e7%94%b0%e5%8d%80/ + + +数据无效(2): 当前第 12 页: 第 233 个无效。与 Childwalker童行者,Ms Catherine Ho,95585099,catherineho33@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab%e6%b8%af%e5%b3%b6%e5%8d%97%e5%8d%80/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e6%95%99%e8%82%b2%e5%b0%8e%e5%b8%ab%e6%b8%af%e5%b3%b6%e5%8d%97%e5%8d%80/ + + +数据无效(2): 当前第 12 页: 第 236 个无效。与 HKTA香港導師會,劉小姐,55961800,sandi.lau@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%ad%b8%e7%b5%90%e4%bb%96%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 12 页: 第 237 个无效。与 HKTA香港導師會,劉小姐,55961800,sandi.lau@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%ad%b8%e7%88%b5%e5%a3%ab%e8%88%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 12 页: 第 238 个无效。与 HKTA香港導師會,劉小姐,55961800,sandi.lau@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%ad%b8%e6%9b%b8%e6%b3%95%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 12 页: 第 239 个无效。与 HKTA香港導師會,劉小姐,55961800,sandi.lau@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%ad%b8%e5%9c%8d%e6%a3%8b%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 12 页: 第 240 个无效。与 HKTA香港導師會,劉小姐,55961800,sandi.lau@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%ad%b8%e6%89%ad%e6%b0%a3%e7%90%83%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 13 页: 第 241 个无效。与 HKTA香港導師會,劉小姐,55961800,sandi.lau@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%ad%b8%e9%a6%96%e9%a3%be%e8%a3%bd%e4%bd%9c%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 13 页: 第 242 个无效。与 大衆教室(田心村),Ms Li,66501338,populartaiwai@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-6/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%ef%bc%89/ + + +数据无效(2): 当前第 13 页: 第 243 个无效。与 HKCCCU Logos Academy,Personnel Department,23372123,jobs@logosacademy.edu.hk,https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e9%bc%93%e9%9a%8a%e5%b0%8e%e5%b8%ab-violin-instructor-cello-instructor-clarinet-instructor-percussion-instructor-2/ + + +数据无效(2): 当前第 13 页: 第 244 个无效。与 Indigo Kids,Miss Lo,97571105,indigo_kids.adm@hotmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e5%bf%83%e5%b0%8e%e5%b8%ab%e5%a4%aa%e5%8f%a4%e5%88%86%e6%a0%a1/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e4%b8%ad%e5%bf%83%e5%b0%8e%e5%b8%ab%e5%85%83%e6%9c%97%e5%8d%80-2/ + + +数据无效(2): 当前第 13 页: 第 246 个无效。与 JEI Diversity Learning Centre(Tsing Yi),Miss Chow,60128411,naturejovial@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%9c%ac%e5%9c%b0%e6%95%b8%e5%ad%b8%e6%88%96%e8%8b%b1%e6%96%87%e5%b0%8e%e5%b8%ab-3/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%9c%ac%e5%9c%b0%e6%95%b8%e5%ad%b8%e6%88%96%e8%8b%b1%e6%96%87%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 13 页: 第 248 个无效。与 廠商會蔡章閣中學,Anson Chan,24520681,ac@cmacck.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%91%a8%e4%b8%89-%e5%91%a8%e5%9b%9b%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab3%e5%90%8d/ + + +数据无效(2): 当前第 13 页: 第 249 个无效。与 香港布廠商會朱石麟中學,Cora Lam,27968323,recruit@chusheklun.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%92%96%e5%95%a1%e6%b2%96%e8%aa%bf%e5%8f%8a%e6%8b%89%e8%8a%b1%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 13 页: 第 250 个无效。与 香港布廠商會朱石麟中學,Cora Lam,27968323,recruit@chusheklun.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%96%ae%e8%bb%8a%e6%95%99%e7%b7%b4/ + + +数据无效(2): 当前第 13 页: 第 252 个无效。与 保良局陸慶濤小學,藍小姐,27010011,cmlam@plklht.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%88%e5%94%b1%e5%9c%98%e6%8c%87%e6%8f%ae%e4%bb%a3%e8%aa%b2-29-11-28-2%e6%9c%9f%e9%96%93/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%86%b0%e7%90%83%e6%95%99%e7%b7%b4/ + + +数据无效(2): 当前第 13 页: 第 253 个无效。与 神召會康樂中學,湯小姐,26520698,application@gc.hebron.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e6%95%b8%e5%ad%b8%e8%a3%9c%e7%bf%92%e7%8f%ad/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e3%80%8c%e9%9b%9c%e8%80%8d%e7%8f%ad%e3%80%8d%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 13 页: 第 254 个无效。与 神召會康樂中學,湯小姐,26520698,application@gc.hebron.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e6%95%b8%e5%ad%b8%e8%a3%9c%e7%bf%92%e7%8f%ad/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e3%80%8c%e8%b7%b3%e8%88%9e%e7%8f%ad%e3%80%8d%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 13 页: 第 257 个无效。与 現代小學士有限公司,MS Poon,97290919,bachelor.junniepoon@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-part-time-tutor-%e9%95%b7%e6%9c%9f-%e7%9f%ad%e6%9c%9f%e4%b9%9f%e5%8f%af/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/full-time-tutor-teacher-%e5%85%a8%e8%81%b7%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 13 页: 第 259 个无效。与 大衆教室佐敦分校(恒豐中心),李小姐,94162842,sinotrend13@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-8/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-3/ + + +数据无效(2): 当前第 14 页: 第 262 个无效。与 HKCCCU Logos Academy,Personnel Department,23372123,jobs@logosacademy.edu.hk,https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e9%bc%93%e9%9a%8a%e5%b0%8e%e5%b8%ab-violin-instructor-cello-instructor-clarinet-instructor-percussion-instructor/ + + +数据无效(2): 当前第 14 页: 第 263 个无效。与 The Hong Kong Council of the Church of Christ in China 中華基督教會香港區會家情軒,丁小姐 / 莊小姐,24414833,fwinfo@hkcccc.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0-5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0-4/ + + +数据无效(2): 当前第 14 页: 第 264 个无效。与 賽馬會萬鈞毅智書院(政府津貼文法中學),黃小姐,24472322,eduyoung@jcmkec.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-11/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 14 页: 第 265 个无效。与 御學軒集團有限公司,Enos Cheng,63604267,hr@rlg.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab-%e6%b2%b9%e9%ba%bb%e5%9c%b0/ + + +数据无效(2): 当前第 14 页: 第 266 个无效。与 御學軒集團有限公司,Enos Cheng,63604267,hr@rlg.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%88%9d%e4%b8%ad%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 14 页: 第 271 个无效。与 新會商會陳白沙紀念中學,李穎沁老師,25535324,hr@cpss.edu.hk,https://recruit.hkfew.org.hk/jobs/2022-2023%e5%ad%b8%e5%b9%b4%e7%94%b7%e7%ab%a5%e8%bb%8d%e5%90%88%e7%b4%84%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/2022-2023%e5%ad%b8%e5%b9%b4%e5%85%bc%e8%81%b7%e8%aa%b2%e5%be%8c%e8%8d%89%e5%9c%b0%e6%bb%be%e7%90%83%e6%95%99%e7%b7%b4/ + + +数据无效(2): 当前第 14 页: 第 273 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e5%a5%a7%e6%95%b8%e5%b0%8e%e5%b8%ab%e6%9c%89%e6%95%99%e5%b8%ab%e7%89%88%e7%ad%94%e6%a1%88__%e6%af%8f%e7%af%80250/ + + +数据无效(2): 当前第 14 页: 第 274 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab_%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e5%b0%8e%e5%b8%ab%e6%9c%89%e6%95%99%e5%b8%ab%e7%89%88%e7%ad%94%e6%a1%88__%e6%af%8f%e7%af%80250/ + + +数据无效(2): 当前第 14 页: 第 275 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e4%b8%ad%e6%96%87%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e4%bd%8d%e5%ad%b8%e7%94%9f%e8%83%bd%e5%8a%9b%e5%8f%aa/ + + +数据无效(2): 当前第 14 页: 第 276 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e5%a5%a7%e6%95%b8%e7%8f%ad%e5%b0%8e%e5%b8%ab%e6%9c%89%e6%95%99%e5%b8%ab%e7%89%88%e7%ad%94%e6%a1%88__%e6%af%8f%e7%af%803/ + + +数据无效(2): 当前第 14 页: 第 277 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e4%b8%ad%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80300/ + + +数据无效(2): 当前第 14 页: 第 278 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80180/ + + +数据无效(2): 当前第 14 页: 第 279 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab_%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e5%8a%9f%e8%bc%94%e7%8f%ad%e4%bb%a3%e8%aa%b2%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80100/ + + +数据无效(2): 当前第 14 页: 第 280 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e8%8b%b1%e6%96%87%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80180-2/ + + +数据无效(2): 当前第 15 页: 第 281 个无效。与 豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e5%a5%a7%e6%95%b8%e7%8f%ad%e5%b0%8e%e5%b8%ab%e6%9c%89%e6%95%99%e5%b8%ab%e7%89%88%e7%ad%94%e6%a1%88__%e6%af%8f%e7%af%801/ + + +数据无效(2): 当前第 15 页: 第 285 个无效。与 華英中學,行政主任麥先生,27607772,recruit@waying.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/tutors-for-speech-festival-coaching/ + + +数据无效(2): 当前第 15 页: 第 286 个无效。与 Happy Workshop樂斯音樂藝術坊,Ann Leung,95406014,happyworkshop09@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 15 页: 第 287 个无效。与 東華三院郭一葦中學,莊小姐,24471258,office@twghkywc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e8%aa%b2%e5%be%8c%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b8%ab%e5%8f%8b%e5%ad%b8%e7%bf%92%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e6%96%87%e3%80%81%e8%8b%b1%e6%96%87%e3%80%81%e6%95%b8%e5%ad%b8%e3%80%81steam%e3%80%81%e8%a6%96%e8%a6%ba%e8%97%9d/ + + +数据无效(2): 当前第 15 页: 第 291 个无效。与 樂善堂余近卿中學,Ms. Yeung,23362657,job@ykh.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%b7%b3%e8%88%9e%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 15 页: 第 293 个无效。与 基督教宣道會華基堂青年中心,黃小姐,25516629,candy@cmawkc.org,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e7%89%b9%e6%ae%8a%e5%ad%b8%e6%a0%a1-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e7%89%b9%e6%ae%8a%e5%ad%b8%e6%a0%a1/ + + +数据无效(2): 当前第 15 页: 第 295 个无效。与 香港紅十字會雅麗珊郡主學校,劉小姐,23401022,pars@redcross.org.hk,https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%a7%91%e8%aa%b2%e5%be%8c%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e7%a7%91%e8%aa%b2%e5%be%8c%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 15 页: 第 296 个无效。与 基督教宣道會華基堂青年中心,黃小姐,25516629,candy@cmawkc.org,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e7%89%b9%e6%ae%8a%e5%ad%b8%e6%a0%a1-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/zoom-%e5%8a%9f%e8%aa%b2%e6%94%af%e6%8f%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b0%8f%e5%ad%b8/ + + +数据无效(2): 当前第 15 页: 第 297 个无效。与 基督教宣道會華基堂青年中心,黃小姐,25516629,candy@cmawkc.org,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e7%89%b9%e6%ae%8a%e5%ad%b8%e6%a0%a1-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e6%94%af%e6%8f%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b0%8f%e5%ad%b8/ + + +数据无效(2): 当前第 15 页: 第 298 个无效。与 Tom Lee Music Co. Ltd,Ophelia Choi,27377530,hr@tomleemusic.com,https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-12/ + + +数据无效(2): 当前第 15 页: 第 299 个无效。与 賽馬會萬鈞毅智書院(政府津貼文法中學),黃小姐,24472322,eduyoung@jcmkec.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-11/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-7/ + + +数据无效(2): 当前第 15 页: 第 300 个无效。与 香港布廠商會朱石麟中學,Cora Lam,27968323,recruit@chusheklun.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e8%8b%b1%e6%95%b8%e7%a7%91%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 16 页: 第 301 个无效。与 賽馬會萬鈞毅智書院(政府津貼文法中學),黃小姐,24472322,eduyoung@jcmkec.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-11/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-7/ + + +数据无效(2): 当前第 16 页: 第 302 个无效。与 香港布廠商會朱石麟中學,Cora Lam,27968323,recruit@chusheklun.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e8%8b%b1%e6%95%b8%e7%a7%91%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 16 页: 第 303 个无效。与 御學軒集團有限公司,Enos Cheng,63604267,hr@rlg.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e6%95%b8%e5%ad%b8%e5%b0%88%e7%a7%91%e7%8f%ad%e5%b0%8e%e5%b8%ab-4/ + + +数据无效(2): 当前第 16 页: 第 304 个无效。与 御學軒集團有限公司,Enos Cheng,63604267,hr@rlg.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%88%b0%e6%a0%a1%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab-4/ + + +数据无效(2): 当前第 16 页: 第 305 个无效。与 御學軒集團有限公司,Enos Cheng,63604267,hr@rlg.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%88%b0%e6%a0%a1%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab%e5%85%83%e5%b1%af%e5%a4%a9/ + + +数据无效(2): 当前第 16 页: 第 306 个无效。与 御學軒集團有限公司,Enos Cheng,63604267,hr@rlg.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8d%8a%e8%81%b7-%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 16 页: 第 307 个无效。与 大衆教室(田心村),Ms Li,66501338,populartaiwai@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-6/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88full-time-part-time/ + + +数据无效(2): 当前第 16 页: 第 308 个无效。与 香港布廠商會朱石麟中學,Cora Lam,27968323,recruit@chusheklun.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 16 页: 第 311 个无效。与 中華基督教會燕京書院,伍小姐,23879988,recruit@yenching.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%81%af%e8%aa%b2%e6%b4%bb%e5%8b%95%e5%8a%a9%e7%90%86%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e8%8b%b1%e6%95%b8%e7%a7%91%e5%8f%8a%e7%94%b7%e5%ad%90%e6%8e%92%e7%90%83%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 16 页: 第 312 个无效。与 東華三院馬振玉紀念中學,梁家熙老師,24439833,leungkaheisimon@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%85%ad%e6%96%87%e6%86%91%e8%a9%a6%e6%95%b8%e5%ad%b8%e6%94%af%e6%8f%b4%e8%aa%b2%e7%a8%8b-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%85%ad%e6%96%87%e6%86%91%e8%a9%a6%e6%95%b8%e5%ad%b8%e6%94%af%e6%8f%b4%e8%aa%b2%e7%a8%8b/ + + +数据无效(2): 当前第 16 页: 第 314 个无效。与 Man Kai Education,Miss Chan,26688294,eunice@monkeymaster.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-4/ + + +数据无效(2): 当前第 16 页: 第 315 个无效。与 Man Kai Education,Miss Chan,26688294,eunice@monkeymaster.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%a8%e8%81%b7%e5%b0%8e%e5%b8%ab-full-time-tutor-4/ + + +数据无效(2): 当前第 16 页: 第 316 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e6%9c%97%e8%aa%a6%e5%b0%8e%e5%b8%ab/ + + +数据无效(1): 当前第 16 页: 第 317 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 16 页: 第 319 个无效。与 樂善堂余近卿中學,Ms. Yeung,23362657,job@ykh.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%85%ad%e7%b4%9a%e7%b6%93%e6%bf%9f%e7%a7%91%e7%b2%be%e9%80%b2%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 16 页: 第 320 个无效。与 樂善堂余近卿中學,Ms. Yeung,23362657,job@ykh.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%ad%8c%e5%94%b1%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 17 页: 第 321 个无效。与 樂善堂余近卿中學,Ms. Yeung,23362657,job@ykh.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%ad%8c%e5%94%b1%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 17 页: 第 322 个无效。与 樂善堂余近卿中學,Ms. Yeung,23362657,job@ykh.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%b4%bb%e5%8b%95%e5%b0%8f%e7%b5%84%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 17 页: 第 323 个无效。与 樂善堂余近卿中學,Ms. Yeung,23362657,job@ykh.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab%e6%88%b2%e5%8a%87%e3%80%81%e8%88%9e%e8%b9%88-2/ + + +数据无效(1): 当前第 17 页: 第 324 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/english-or-math-tutor/ + + +数据无效(2): 当前第 17 页: 第 326 个无效。与 東華三院馬振玉紀念中學,鄭慶珠老師,24439833,chenghingchu@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-3/ + + +数据无效(2): 当前第 17 页: 第 327 个无效。与 余振強紀念中學,楊竣皓,55340585,chyeung@gm.yckmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%a2%9e%e7%9b%8a%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e4%b8%ad%e6%96%87%ef%bc%89-7/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a2%9e%e7%9b%8a%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e8%8b%b1%e6%96%87%ef%bc%89-3/ + + +数据无效(2): 当前第 17 页: 第 328 个无效。与 余振強紀念中學,楊竣皓,55340585,chyeung@gm.yckmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%a2%9e%e7%9b%8a%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e4%b8%ad%e6%96%87%ef%bc%89-7/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a2%9e%e7%9b%8a%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e4%b8%ad%e6%96%87%ef%bc%89-6/ + + +数据无效(2): 当前第 17 页: 第 329 个无效。与 天主教培聖中學,宋小姐,24450800,info@puishing.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%89%af%e6%95%99%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7%ef%bc%89%ef%bc%8f%e9%ab%98%e4%b8%ad%e4%b8%ad%e6%96%87%e5%a2%9e%e6%bd%a4%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%bc/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e9%ab%98%e4%b8%ad%e5%9c%b0%e7%90%86%e7%a7%91%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 17 页: 第 334 个无效。与 大衆教室佐敦分校(恒豐中心),李小姐,94162842,sinotrend13@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-8/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 17 页: 第 336 个无效。与 Children initiation Association Education Centre,湯先生,64435475,Hkcia.edu@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%88%e7%a7%91-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e7%b5%84%e5%b0%88%e7%a7%91%e7%b5%b1%e7%b1%8c%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8/ + + +数据无效(2): 当前第 17 页: 第 337 个无效。与 The Hong Kong Council of the Church of Christ in China 中華基督教會香港區會家情軒,丁小姐 / 莊小姐,24414833,fwinfo@hkcccc.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0-5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0-3/ + + +数据无效(2): 当前第 17 页: 第 338 个无效。与 聖公會聖西門呂明才中學,李小姐,24598236/39567900,recruit@skhsslmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%96%87%e6%86%91%e8%a9%a6%e8%8b%b1%e8%aa%9e%e7%89%b9%e8%a8%93%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%88%e5%94%b1%e5%9c%98%e9%8b%bc%e7%90%b4%e4%bc%b4%e5%a5%8f%e5%b0%8e%e5%b8%ab2022-2023/ + + +数据无效(2): 当前第 18 页: 第 341 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%b0%8f%e5%ad%b8%e7%8f%a0%e5%bf%83%e7%ae%97%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80200/ + + +数据无效(2): 当前第 18 页: 第 342 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab_%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e7%8f%ad__%e6%af%8f%e7%af%80150/ + + +数据无效(2): 当前第 18 页: 第 343 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab_%e5%b0%8f%e5%ad%b8%e6%97%a5%e6%96%87%e7%8f%ad__%e6%af%8f%e7%af%80200/ + + +数据无效(2): 当前第 18 页: 第 344 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e4%b8%ad%e6%96%87%e5%af%ab%e4%bd%9c%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80250/ + + +数据无效(2): 当前第 18 页: 第 345 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab_%e5%b0%8f%e5%ad%b8%e8%ac%9b%e6%95%85%e4%ba%8b%e7%8f%ad__%e6%af%8f%e7%af%80100/ + + +数据无效(2): 当前第 18 页: 第 346 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab_%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad__%e6%af%8f%e7%af%80150/ + + +数据无效(2): 当前第 18 页: 第 347 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab_%e5%88%9d%e4%b8%ad%e7%a4%be%e6%96%87%e7%8f%ad__%e6%af%8f%e7%af%80250/ + + +数据无效(2): 当前第 18 页: 第 348 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab__%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80140/ + + +数据无效(2): 当前第 18 页: 第 349 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab__%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80100/ + + +数据无效(2): 当前第 18 页: 第 350 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ + + +数据无效(2): 当前第 18 页: 第 351 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e4%b8%ad%e8%8b%b1%e6%95%b8%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80180/ + + +数据无效(2): 当前第 18 页: 第 352 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e9%ab%98%e4%b8%ad%e4%b8%ad%e6%96%87%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80250/ + + +数据无效(2): 当前第 18 页: 第 353 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%b0%8f%e5%ad%b8%e8%8b%b1%e6%96%87%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ + + +数据无效(2): 当前第 18 页: 第 354 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e8%8b%b1%e6%96%87%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80180/ + + +数据无效(2): 当前第 18 页: 第 355 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e9%ab%98%e4%b8%ad%e8%8b%b1%e6%96%87%e5%8f%a3%e8%a9%a6__%e6%af%8f%e7%af%80180/ + + +数据无效(2): 当前第 18 页: 第 356 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e4%b8%ad%e6%96%87__%e6%af%8f%e7%af%80250/ + + +数据无效(2): 当前第 18 页: 第 357 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e6%95%b8%e5%a5%a7%e6%95%b8__%e6%af%8f%e7%af%80200/ + + +数据无效(2): 当前第 18 页: 第 359 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7online%e5%b0%8e%e5%b8%ab__%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8__%e6%af%8f%e7%af%80150/ + + +数据无效(2): 当前第 18 页: 第 360 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e6%95%b8%e5%ad%b8__%e6%af%8f%e7%af%80180/ + + +数据无效(2): 当前第 19 页: 第 361 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%88%9d%e4%b8%ad%e6%95%b8%e5%ad%b8__%e6%af%8f%e7%af%80250/ + + +数据无效(2): 当前第 19 页: 第 362 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8__%e6%99%82%e8%96%aa180/ + + +数据无效(2): 当前第 19 页: 第 363 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e9%80%9a%e8%ad%98%e7%a7%91__%e6%99%82%e8%96%aa300/ + + +数据无效(2): 当前第 19 页: 第 364 个无效。与 豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e6%95%b8%e5%ad%b8%e5%b0%8e%e5%b8%ab%e6%99%82%e8%96%aa180/ + + +数据无效(2): 当前第 19 页: 第 366 个无效。与 HKTA香港導師會,李小姐,21142188,april.lee@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%a4%a9%e6%b0%b4%e5%9c%8d%e5%8d%80%e4%b8%ad%e5%ad%b8%e4%b8%ad%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%95%b7%e6%b2%99%e7%81%a3%e5%8d%80%e5%b0%8f%e7%89%b9%e6%ae%8a%e6%95%99%e8%82%b2%ef%bc%88sen%ef%bc%89%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 19 页: 第 369 个无效。与 梁式芝書院,鄭小姐,23496626,recruitment@lscc.edu.hk,https://recruit.hkfew.org.hk/jobs/enhancement-class-tutors-for-students-after-school/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%a8%82%e7%ac%9b%e5%ad%90%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 19 页: 第 370 个无效。与 山林教育Hillwood Tutorial School,Miss Amanda Ng,23779937,hillwoodtst@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-4/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8d%8a%e8%81%b7%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 19 页: 第 371 个无效。与 聖雅各福群會 延續教育中心,Daphne Suen,59885027,daphne.suen@sjs.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e5%b0%88%e7%a7%91%e7%8f%ad%e8%8d%83%e7%81%a3%e3%80%81%e8%91%b5%e6%b6%8c%e5%8d%80-%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99%e5%b8%ab-6/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e3%80%8e%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e3%80%8f%e5%b1%af%e9%96%80%e5%b8%82%e4%b8%ad%e5%bf%83-%e4%b8%80%e8%87%b3%e5%9b%9b/ + + +数据无效(2): 当前第 19 页: 第 372 个无效。与 港九潮州公會中學,Emily Ngan,23964187,recruit@ccpass.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%ad%b8%e7%bf%92%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e3%80%81%e8%8b%b1%e3%80%81%e6%95%b8-%e8%aa%b2%e5%be%8c%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 19 页: 第 373 个无效。与 小星光兒童教育中心,文先生,52169183,mylittlestar062020@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%ad%b8%e5%89%8d-%e5%b0%8f%e5%ad%b8%e7%a0%94%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%ad%b8%e5%89%8d%e5%b0%8f%e5%ad%b8%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 19 页: 第 374 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a4%a7%e6%8f%90%e7%90%b4%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 19 页: 第 375 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e6%8f%90%e7%90%b4%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 19 页: 第 376 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%a5%bf%e6%b4%8b%e6%95%b2%e6%93%8a%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 19 页: 第 377 个无效。与 明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab-%e5%88%9d%e4%b8%ad%e3%80%81%e9%ab%98%e4%b8%ad%e6%a0%b8%e5%bf%83%e7%a7%91%e5%8f%8a%e9%81%b8%e4%bf%ae%e7%a7%91-5%e4%bd%8d-2/ + + +数据无效(2): 当前第 19 页: 第 378 个无效。与 明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e7%a7%91%e8%b3%87%e5%84%aa%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-3/ + + +数据无效(2): 当前第 19 页: 第 379 个无效。与 明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%a7%91%e8%b3%87%e5%84%aa%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-4/ + + +数据无效(2): 当前第 20 页: 第 382 个无效。与 東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%9c%a8%e7%ae%a1%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 20 页: 第 383 个无效。与 Tom Lee Music Co. Ltd,Ophelia Choi,27377530,hr@tomleemusic.com,https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-11/ + + +数据无效(2): 当前第 20 页: 第 384 个无效。与 數研喜樂教育中心,陳先生,36197929,blissful@soinedu.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e6%95%b8%e5%ad%b8%e5%b0%8e%e5%b8%ab-maths-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/primary-english-tutor-flexible-working-hours/ + + +数据无效(2): 当前第 20 页: 第 385 个无效。与 數研喜樂教育中心,陳先生,36197929,blissful@soinedu.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e6%95%b8%e5%ad%b8%e5%b0%8e%e5%b8%ab-maths-tutor/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b0%8f%e5%ad%b8%e6%95%b8%e5%ad%b8%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 20 页: 第 387 个无效。与 東華三院郭一葦中學,莊小姐,24471258,office@twghkywc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e8%aa%b2%e5%be%8c%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/english-enrichment-programmes-tutor/ + + +数据无效(2): 当前第 20 页: 第 388 个无效。与 Man Kai Education,Miss Chan,26688294,eunice@monkeymaster.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-3/ + + +数据无效(2): 当前第 20 页: 第 389 个无效。与 Man Kai Education,Miss Chan,26688294,eunice@monkeymaster.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%a8%e8%81%b7%e5%b0%8e%e5%b8%ab-full-time-tutor-3/ + + +数据无效(2): 当前第 20 页: 第 391 个无效。与 聖士提反女子中學,Ms. Josephine Yuen,25492521,ssgcpost@ssgc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%8e%a1%e8%a8%aa%e5%8f%8a%e7%b7%a8%e8%bc%af%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 20 页: 第 395 个无效。与 Childwalker童行者,Ms Catherine Ho,95585099,catherineho33@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab%e6%b8%af%e5%b3%b6%e5%8d%97%e5%8d%80/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b9%bc%e5%85%92%e6%95%99%e8%82%b2%e5%b0%8e%e5%b8%ab%e6%b8%af%e5%b3%b6%e5%8d%80/ + + +数据无效(2): 当前第 21 页: 第 401 个无效。与 救世軍,人力資源部,27832366,hrd.recruit@hkm.salvationarmy.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9d%91%e6%9d%b1%e9%9a%8a%e6%95%99%e6%9c%83-%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-thtc-05-22/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e4%b9%9d%e9%be%8d%e5%9f%8e%e9%9a%8a%e7%a4%be%e5%8d%80%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-kcityc-02/ + + +数据无效(2): 当前第 21 页: 第 403 个无效。与 東華三院馬振玉紀念中學,鄭慶珠老師,24439833,chenghingchu@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-2/ + + +数据无效(2): 当前第 21 页: 第 404 个无效。与 樂善堂余近卿中學,Ms. Yeung,23362657,job@ykh.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e7%b6%93%e6%bf%9f%e7%a7%91%e7%b2%be%e9%80%b2%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 21 页: 第 405 个无效。与 香港四邑商工總會陳南昌紀念中學,林小姐,27410326,info@cnc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%88%9d%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 21 页: 第 407 个无效。与 救世軍,人力資源部,27832366,hrd.recruit@hkm.salvationarmy.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9d%91%e6%9d%b1%e9%9a%8a%e6%95%99%e6%9c%83-%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-thtc-05-22/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e9%8c%a6%e7%94%b0%e9%9a%8a%e7%a4%be%e5%8d%80%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-ktcec-7-22/ + + +数据无效(2): 当前第 21 页: 第 408 个无效。与 One Plus One Educational Centre,Miss Lau,22468139,wts@1plus1edu.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab-%e5%bd%a9%e8%99%b9-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab-%e5%bd%a9%e8%99%b9/ + + +数据无效(2): 当前第 21 页: 第 409 个无效。与 救世軍,人力資源部,27832366,hrd.recruit@hkm.salvationarmy.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9d%91%e6%9d%b1%e9%9a%8a%e6%95%99%e6%9c%83-%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-thtc-05-22/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e6%9d%b1%e6%b6%8c%e9%9a%8a%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-tccsc-08-22/ + + +数据无效(2): 当前第 21 页: 第 410 个无效。与 聖士提反女子中學,Ms. Josephine Yuen,25492521,ssgcpost@ssgc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/mathematics-tutor-part-time-2/ + + +数据无效(2): 当前第 21 页: 第 413 个无效。与 ENGLISH EXPRESS EDUCATION CENTRE,Miss Lam,54265083,tutor.com.hk@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%8a%e9%96%80%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e6%99%ae%e9%80%9a%e8%a9%b1/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e5%85%bc%e8%81%b7%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e7%89%b9%e6%ae%8a%e6%95%99%e8%82%b2-%e6%95%99%e5%ad%b8%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 21 页: 第 416 个无效。与 新家園協會賽馬會港島東服務中心,黎姑娘,28072188,hr.hkisc@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e6%8f%90%e5%8d%87%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%88%9d%e4%b8%ad/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e5%8f%a3%e8%aa%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%9f%ba%e7%a4%8e%e7%8f%ad-%e9%80%b2%e9%9a%8e%e7%8f%ad/ + + +数据无效(2): 当前第 21 页: 第 417 个无效。与 新家園協會賽馬會港島東服務中心,黎姑娘,28072188,hr.hkisc@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e6%8f%90%e5%8d%87%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%88%9d%e4%b8%ad/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a5%a7%e6%95%b8%e5%9f%ba%e7%a4%8e%e7%8f%ad-%e9%80%b2%e9%9a%8e%e7%8f%ad%e5%b0%8f%e5%ad%b8/ + + +数据无效(2): 当前第 21 页: 第 1 个无效。与 救世軍, 人力資源部, 27832366, hrd.recruit@hkm.salvationarmy.org, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9d%91%e6%9d%b1%e9%9a%8a%e6%95%99%e6%9c%83-%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-thtc-05-22/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e4%b9%9d%e9%be%8d%e5%9f%8e%e9%9a%8a%e7%a4%be%e5%8d%80%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-kcityc-02/ + + +数据无效(2): 当前第 21 页: 第 2 个无效。与 鄰舍輔導會 賽馬會天水圍綜合服務中心, 嚴姑娘, 26178816, tssc@naac.org.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-9/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-9/ + + +数据无效(2): 当前第 21 页: 第 3 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-2/ + + +数据无效(2): 当前第 21 页: 第 1 个无效。与 救世軍, 人力資源部, 27832366, hrd.recruit@hkm.salvationarmy.org, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9d%91%e6%9d%b1%e9%9a%8a%e6%95%99%e6%9c%83-%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-thtc-05-22/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e4%b9%9d%e9%be%8d%e5%9f%8e%e9%9a%8a%e7%a4%be%e5%8d%80%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-kcityc-02/ + + +数据无效(2): 当前第 21 页: 第 2 个无效。与 鄰舍輔導會 賽馬會天水圍綜合服務中心, 嚴姑娘, 26178816, tssc@naac.org.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-9/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-9/ + + +数据无效(2): 当前第 21 页: 第 3 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-2/ + + +数据无效(2): 当前第 21 页: 第 4 个无效。与 樂善堂余近卿中學, Ms. Yeung, 23362657, job@ykh.edu.hk, https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e7%b6%93%e6%bf%9f%e7%a7%91%e7%b2%be%e9%80%b2%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 21 页: 第 5 个无效。与 香港四邑商工總會陳南昌紀念中學, 林小姐, 27410326, info@cnc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%88%9d%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 21 页: 第 6 个无效。与 皇仁舊生會中學, 黃小姐, 24975688, info@qcobass.edu.hk, https://recruit.hkfew.org.hk/jobs/english-classes-tutors/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/english-classes-tutors/ + + +数据无效(2): 当前第 21 页: 第 1 个无效。与 救世軍, 人力資源部, 27832366, hrd.recruit@hkm.salvationarmy.org, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9d%91%e6%9d%b1%e9%9a%8a%e6%95%99%e6%9c%83-%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-thtc-05-22/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e4%b9%9d%e9%be%8d%e5%9f%8e%e9%9a%8a%e7%a4%be%e5%8d%80%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-kcityc-02/ + + +数据无效(2): 当前第 21 页: 第 2 个无效。与 鄰舍輔導會 賽馬會天水圍綜合服務中心, 嚴姑娘, 26178816, tssc@naac.org.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-9/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-9/ + + +数据无效(2): 当前第 21 页: 第 3 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-2/ + + +数据无效(2): 当前第 21 页: 第 4 个无效。与 樂善堂余近卿中學, Ms. Yeung, 23362657, job@ykh.edu.hk, https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e7%b6%93%e6%bf%9f%e7%a7%91%e7%b2%be%e9%80%b2%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 21 页: 第 5 个无效。与 香港四邑商工總會陳南昌紀念中學, 林小姐, 27410326, info@cnc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%88%9d%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 21 页: 第 6 个无效。与 皇仁舊生會中學, 黃小姐, 24975688, info@qcobass.edu.hk, https://recruit.hkfew.org.hk/jobs/english-classes-tutors/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/english-classes-tutors/ + + +数据无效(2): 当前第 21 页: 第 7 个无效。与 救世軍, 人力資源部, 27832366, hrd.recruit@hkm.salvationarmy.org, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9d%91%e6%9d%b1%e9%9a%8a%e6%95%99%e6%9c%83-%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-thtc-05-22/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e9%8c%a6%e7%94%b0%e9%9a%8a%e7%a4%be%e5%8d%80%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-ktcec-7-22/ + + +数据无效(2): 当前第 21 页: 第 8 个无效。与 One Plus One Educational Centre, Miss Lau, 22468139, wts@1plus1edu.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab-%e5%bd%a9%e8%99%b9-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab-%e5%bd%a9%e8%99%b9/ + + +数据无效(2): 当前第 21 页: 第 9 个无效。与 救世軍, 人力資源部, 27832366, hrd.recruit@hkm.salvationarmy.org, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9d%91%e6%9d%b1%e9%9a%8a%e6%95%99%e6%9c%83-%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-thtc-05-22/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e6%9d%b1%e6%b6%8c%e9%9a%8a%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-tccsc-08-22/ + + +数据无效(2): 当前第 21 页: 第 10 个无效。与 聖士提反女子中學, Ms. Josephine Yuen, 25492521, ssgcpost@ssgc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/mathematics-tutor-part-time-2/ + + +数据无效(2): 当前第 21 页: 第 11 个无效。与 香港中華基督教青年會顯徑會所, 莊季陶, 92191989, ktchong@ymca.org.hk, https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab-4/ + + +数据无效(2): 当前第 21 页: 第 12 个无效。与 THE BLUE GALLERY (CWB)LIMITED, PAULA LO, 96277997, info@xiaoyanschoolofart.com.hk, https://recruit.hkfew.org.hk/jobs/%e3%80%90%e7%95%ab%e7%8f%ad%e5%b0%8e%e5%b8%ab%e3%80%91full-time/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e3%80%90%e7%95%ab%e7%8f%ad%e5%b0%8e%e5%b8%ab%e3%80%91full-time/ + + +数据无效(2): 当前第 21 页: 第 13 个无效。与 ENGLISH EXPRESS EDUCATION CENTRE, Miss Lam, 54265083, tutor.com.hk@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%8a%e9%96%80%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e6%99%ae%e9%80%9a%e8%a9%b1/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e5%85%bc%e8%81%b7%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e7%89%b9%e6%ae%8a%e6%95%99%e8%82%b2-%e6%95%99%e5%ad%b8%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 21 页: 第 14 个无效。与 培星園教育中心, Sylvia Lo, 60798428, stargardeneducation@gmail.com, https://recruit.hkfew.org.hk/jobs/%e6%95%99%e5%b8%ab-%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%95%99%e5%b8%ab-%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 21 页: 第 15 个无效。与 新家園協會賽馬會港島東服務中心, 黎姑娘, 28072188, hr.hkisc@gmail.com, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e6%8f%90%e5%8d%87%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%88%9d%e4%b8%ad/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e6%8f%90%e5%8d%87%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%88%9d%e4%b8%ad/ + + +数据无效(2): 当前第 21 页: 第 16 个无效。与 新家園協會賽馬會港島東服務中心, 黎姑娘, 28072188, hr.hkisc@gmail.com, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e6%8f%90%e5%8d%87%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%88%9d%e4%b8%ad/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e5%8f%a3%e8%aa%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%9f%ba%e7%a4%8e%e7%8f%ad-%e9%80%b2%e9%9a%8e%e7%8f%ad/ + + +数据无效(2): 当前第 21 页: 第 17 个无效。与 新家園協會賽馬會港島東服務中心, 黎姑娘, 28072188, hr.hkisc@gmail.com, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e6%8f%90%e5%8d%87%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%88%9d%e4%b8%ad/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a5%a7%e6%95%b8%e5%9f%ba%e7%a4%8e%e7%8f%ad-%e9%80%b2%e9%9a%8e%e7%8f%ad%e5%b0%8f%e5%ad%b8/ + + +数据无效(2): 当前第 21 页: 第 18 个无效。与 Seasons Music Studio, Mr. So, 93003391, seasonsmusicstudio@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%8f%8a%e5%85%bc%e8%81%b7%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%8f%8a%e5%85%bc%e8%81%b7%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 22 页: 第 21 个无效。与 中華基督教會何福堂書院, 江老師, 24596354, hft-mail@hftc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e6%8e%92%e7%90%83%e6%a0%a1%e9%9a%8a%e6%9c%8d%e5%8b%99%e5%93%a1/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%a0%e8%81%98%e4%b9%92%e4%b9%93%e7%90%83%e6%a0%a1%e9%9a%8a%e6%95%99%e7%b7%b4/ + + +数据无效(2): 当前第 22 页: 第 22 个无效。与 The Hong Kong Council of the Church of Christ in China 中華基督教會香港區會家情軒, 丁小姐 / 莊小姐, 24414833, fwinfo@hkcccc.org, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0-5/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0-2/ + + +数据无效(2): 当前第 22 页: 第 23 个无效。与 HKCCCU Logos Academy, Personnel Department, 23372123, jobs@logosacademy.edu.hk, https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%b6%e6%9c%8d%e5%9c%98%e9%ab%94%e5%b0%8e%e5%b8%ab-3/ + + +数据无效(2): 当前第 22 页: 第 24 个无效。与 基督教香港信義會元朗信義中學, 陳貴月小姐 (秘書), 24480622, tswyllss@yahoo.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%80%9a%e8%ad%98%e6%95%99%e8%82%b2%e7%a7%91%e8%aa%b2%e5%be%8c%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 22 页: 第 26 个无效。与 Man Kai Education, Miss Chan, 26688294, eunice@monkeymaster.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%a8%e8%81%b7%e5%b0%8e%e5%b8%ab-full-time-tutor-2/ + + +数据无效(2): 当前第 22 页: 第 27 个无效。与 Man Kai Education, Miss Chan, 26688294, eunice@monkeymaster.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-2/ + + +数据无效(2): 当前第 22 页: 第 28 个无效。与 Tom Lee Music Co. Ltd, Ophelia Choi, 27377530, hr@tomleemusic.com, https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-10/ + + +数据无效(2): 当前第 22 页: 第 29 个无效。与 東華三院邱金元中學, 楊先生 / 吳小姐, 26497385, recruit@twyky.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e7%a7%91%e6%96%87%e6%86%91%e8%a9%a6%e6%8f%90%e6%98%87%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e5%85%ad%e7%b4%9a-2/ + + +数据无效(2): 当前第 22 页: 第 33 个无效。与 彧恩軒(中作教室), 林老師, 97017907, yuenxuan000@gmail.com, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e4%bd%9c%e6%96%87%e8%80%81%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e4%bd%9c%e6%96%87%e8%80%81%e5%b8%ab-3/ + + +数据无效(2): 当前第 22 页: 第 35 个无效。与 基督教香港信義會元朗信義中學, 陳貴月小姐 (秘書), 24480622, tswyllss@yahoo.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%ab%94%e8%82%b2%e6%b4%bb%e5%8b%95%e5%b0%88%e6%a5%ad%e5%b8%b6%e9%9a%8a%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 22 页: 第 37 个无效。与 卓峰優越教育中心,Mr Lam,93173553,recruitment@apex.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab-phy-chem-bio-math/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + + +数据无效(1): 当前第 22 页: 第 38 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e8%be%af%e8%ab%96%e7%8f%ad%e5%b0%8e%e5%b8%ab22-23%e5%b9%b4%e5%ba%a6/ + + +数据无效(2): 当前第 22 页: 第 39 个无效。与 HKCCCU Logos Academy, Personnel Department, 23372123, jobs@logosacademy.edu.hk, https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/f-6-hkdse-english-remedial-tutor/ + + +数据无效(2): 当前第 23 页: 第 41 个无效。与 香港真光書院, 莫潔儀老師, 28711214, hktlcoff@hkstar.com, https://recruit.hkfew.org.hk/jobs/%e8%88%9e%e5%8f%b0%e9%9f%b3%e9%9f%bf%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/dse-ict%e8%aa%b2%e7%a8%8b%e5%8f%8astem%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 23 页: 第 44 个无效。与 佛教何南金中學, 莊小姐, 23400871, recruit@bhnkc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%84%e9%a0%85%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 23 页: 第 45 个无效。与 佛教何南金中學, 莊小姐, 23400871, recruit@bhnkc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%84%e9%a0%85%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 47 个无效。与 佛教何南金中學, 莊小姐, 23400871, recruit@bhnkc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e7%a7%91%e5%b0%88%e7%a7%91%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 48 个无效。与 匯成教育中心(IBEC), 葉小姐, 26777778/54119779, adminibec@biznetvigator.com, https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e7%a8%9a%e5%9c%92%e6%95%99%e5%b8%ab%e5%8f%8a%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab%e5%85%a8%e8%81%b7/ + + +数据无效(2): 当前第 23 页: 第 49 个无效。与 聖士提反女子中學, Ms. Josephine Yuen, 25492521, ssgcpost@ssgc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e8%aa%b2%e9%a4%98%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 23 页: 第 50 个无效。与 奇趣學藝坊有限公司, 薜小姐, 25200353, info.school.hkclp@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%b0%87%e8%bb%8d%e6%be%b3%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad-%ef%bc%88%e8%8d%83%e7%81%a3%ef%bc%89%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 56 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a/ + + +数据无效(1): 当前第 23 页: 第 57 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e7%b6%93%e9%a9%97%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 1 个无效。与 香港真光書院, 莫潔儀老師, 28711214, hktlcoff@hkstar.com, https://recruit.hkfew.org.hk/jobs/%e8%88%9e%e5%8f%b0%e9%9f%b3%e9%9f%bf%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/dse-ict%e8%aa%b2%e7%a8%8b%e5%8f%8astem%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 23 页: 第 2 个无效。与 鳳溪第一中學, 馬小姐, 26700366, fk1ss@fk1ss.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e4%b8%bb%e7%a7%91/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e4%b8%bb%e7%a7%91/ + + +数据无效(2): 当前第 23 页: 第 3 个无效。与 聖公會林裘謀中學, 郭小姐, 26488222, skhlkmss@skhlkmss.edu.hk, https://recruit.hkfew.org.hk/jobs/coaches-for-english-public-speaking-solo-verse-speaking/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/coaches-for-english-public-speaking-solo-verse-speaking/ + + +数据无效(2): 当前第 23 页: 第 4 个无效。与 佛教何南金中學, 莊小姐, 23400871, recruit@bhnkc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%84%e9%a0%85%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 23 页: 第 5 个无效。与 佛教何南金中學, 莊小姐, 23400871, recruit@bhnkc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%84%e9%a0%85%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 6 个无效。与 香港布廠商會朱石麟中學, 林小姐, 27968323, lamwf@chusheklun.edu.hk, https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e7%8f%ad%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e7%8f%ad%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 7 个无效。与 佛教何南金中學, 莊小姐, 23400871, recruit@bhnkc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e7%a7%91%e5%b0%88%e7%a7%91%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 8 个无效。与 匯成教育中心(IBEC), 葉小姐, 26777778/54119779, adminibec@biznetvigator.com, https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e7%a8%9a%e5%9c%92%e6%95%99%e5%b8%ab%e5%8f%8a%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab%e5%85%a8%e8%81%b7/ + + +数据无效(2): 当前第 23 页: 第 9 个无效。与 聖士提反女子中學, Ms. Josephine Yuen, 25492521, ssgcpost@ssgc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e8%aa%b2%e9%a4%98%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 23 页: 第 10 个无效。与 奇趣學藝坊有限公司, 薜小姐, 25200353, info.school.hkclp@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%b0%87%e8%bb%8d%e6%be%b3%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad-%ef%bc%88%e8%8d%83%e7%81%a3%ef%bc%89%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 11 个无效。与 Bishop Hall Jubilee School, Ms. Wong, 23363034, hr@bhjs.edu.hk, https://recruit.hkfew.org.hk/jobs/tutors-for-enhancement-classes-after-school-classes-saturday-remedial-classes/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/tutors-for-enhancement-classes-after-school-classes-saturday-remedial-classes/ + + +数据无效(2): 当前第 23 页: 第 12 个无效。与 天主教聖華學校, 何敏淇老師, 26924593, hmk@littleflowerschool.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%90%88%e5%94%b1%e5%9c%98%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%90%88%e5%94%b1%e5%9c%98%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 13 个无效。与 Tak Sun Secondary School, Mr Mervyn Lam, 23174339, hrd@tsss.edu.hk, https://recruit.hkfew.org.hk/jobs/coaches-and-course-tutors-service-agreement-based-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/coaches-and-course-tutors-service-agreement-based-4/ + + +数据无效(2): 当前第 23 页: 第 14 个无效。与 石籬天主教中學, 黃小姐, 24291221, email@slc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e6%94%af%e6%8f%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e6%94%af%e6%8f%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 23 页: 第 15 个无效。与 大衆教室(田心村), Ms Li, 67759354, populartaiwai@gmail.com, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89/ + + +数据无效(2): 当前第 23 页: 第 16 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a/ + + +数据无效(1): 当前第 23 页: 第 17 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e7%b6%93%e9%a9%97%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 18 个无效。与 藍田街坊福利會, 盧承依女士, 27723163, hongyingkinder@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-12/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-12/ + + +数据无效(2): 当前第 23 页: 第 19 个无效。与 大衆教室(田心村), Fanny Li, 67759354, yffanny@gmail.com, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e5%8f%8a%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e5%8f%8a%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 23 页: 第 20 个无效。与 小月亮創作室, 邱小姐, 68781132, littlemoonart@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e7%95%ab%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e7%95%ab%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 24 页: 第 21 个无效。与 匯成教育中心(IBEC), 葉小姐, 26777778/54119779, adminibec@biznetvigator.com, https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89/ + + +数据无效(2): 当前第 24 页: 第 22 个无效。与 天主教培聖中學, 宋小姐, 24450800, info@puishing.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%89%af%e6%95%99%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7%ef%bc%89%ef%bc%8f%e9%ab%98%e4%b8%ad%e4%b8%ad%e6%96%87%e5%a2%9e%e6%bd%a4%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%bc/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%b6%93%e6%bf%9f%e7%a7%91%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 24 页: 第 23 个无效。与 Mood Education (Prince Edward), Ms Chan, 84904568, moodventure2022@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%8d%8a%e8%81%b7%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 24 页: 第 25 个无效。与 循道衛理楊震社會服務處 家庭健康教育及輔導中心, 黃先生, 21714111, leowong@yang.org.hk, https://recruit.hkfew.org.hk/jobs/%e7%be%a9%e5%b7%a5%e8%a8%88%e5%8a%83%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e6%92%9e%e5%bc%8f%e6%ac%96%e7%90%83%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 24 页: 第 26 个无效。与 循道衛理楊震社會服務處 家庭健康教育及輔導中心, 黃先生, 21714111, leowong@yang.org.hk, https://recruit.hkfew.org.hk/jobs/%e7%be%a9%e5%b7%a5%e8%a8%88%e5%8a%83%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%ba%b2%e9%81%bf%e7%9b%a4%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 24 页: 第 29 个无效。与 NEW ASIA, Miss Wong/ Mr. Tang, 64322545, hkg.edu.hk@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e9%9f%b3%e6%a8%82%e7%8f%ad-%e8%97%9d%e8%a1%93%e7%8f%ad-%e9%81%8b%e5%8b%95%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e8%88%9e%e8%b9%88%e7%8f%ad%e5%b0%8e%e5%b8%ab-hip-hop-k-pop/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e9%9f%b3%e6%a8%82%e7%8f%ad-%e8%97%9d%e8%a1%93%e7%8f%ad-%e9%81%8b%e5%8b%95%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e7%a4%be%e5%b7%a5-%e5%bf%83%e7%90%86%e8%bc%94%e5%b0%8e%e5%93%a1/ + + +数据无效(2): 当前第 24 页: 第 31 个无效。与 天主教新民書院,羅小姐,23857812,ncc@newman.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%8e%92%e7%90%83%e9%9a%8a%e6%95%99%e7%b7%b4-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b9%92%e4%b9%93%e7%90%83%e9%9a%8a%e6%95%99%e7%b7%b4/ + + +数据无效(2): 当前第 24 页: 第 32 个无效。与 香港真光書院, 莫潔儀老師, 28711214, hktlcoff@hkstar.com, https://recruit.hkfew.org.hk/jobs/%e8%88%9e%e5%8f%b0%e9%9f%b3%e9%9f%bf%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%85%ad%e8%aa%b2%e5%be%8c%e5%af%ab%e4%bd%9c%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 24 页: 第 34 个无效。与 余振強紀念中學, 楊竣皓, 55340585, chyeung@gm.yckmc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%a2%9e%e7%9b%8a%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e4%b8%ad%e6%96%87%ef%bc%89-7/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a2%9e%e7%9b%8a%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e4%b8%ad%e6%96%87%ef%bc%89-5/ + + +数据无效(2): 当前第 24 页: 第 35 个无效。与 余振強紀念中學, 楊竣皓, 55340585, chyeung@gm.yckmc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%a2%9e%e7%9b%8a%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e4%b8%ad%e6%96%87%ef%bc%89-7/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a2%9e%e7%9b%8a%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e8%8b%b1%e6%96%87%ef%bc%89-2/ + + +数据无效(2): 当前第 24 页: 第 36 个无效。与 連青網絡-香港神託會, 黃小姐, 26371866, aster.wong@stewards.hk, https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%a2%a9%e9%96%80%ef%bc%89-6/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%a2%a9%e9%96%80%ef%bc%89-5/ + + +数据无效(2): 当前第 24 页: 第 37 个无效。与 順德聯誼總會翁祐中學, 姚小姐, 31570632, info@stfa-yyc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e8%aa%b2%e5%be%8c%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 24 页: 第 38 个无效。与 順德聯誼總會翁祐中學, 姚小姐, 31570632, info@stfa-yyc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/it%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 24 页: 第 39 个无效。与 華英中學, 行政主任麥先生, 27607772, recruit@waying.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%84%e7%a7%91%e5%ad%b8%e8%a1%93%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 24 页: 第 40 个无效。与 文理書院(香港), 校務處, 25567413, cognitiohk@cognitiohk.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9b%9b%e7%b4%9a%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%be%af%e8%ab%96%e8%a8%93%e7%b7%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 25 页: 第 41 个无效。与 香港紅十字會雅麗珊郡主學校, 劉小姐, 23401022, pars@redcross.org.hk, https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%a7%91%e8%aa%b2%e5%be%8c%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b7%a5%e4%bd%9c%e8%a8%93%e7%b7%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 25 页: 第 42 个无效。与 彧恩軒(中作教室), 林老師, 97017907, yuenxuan000@gmail.com, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e4%bd%9c%e6%96%87%e8%80%81%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e4%bd%9c%e6%96%87%e8%80%81%e5%b8%ab-2/ + + +数据无效(2): 当前第 25 页: 第 47 个无效。与 連青網絡-香港神託會, 黃小姐, 26371866, aster.wong@stewards.hk, https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%a2%a9%e9%96%80%ef%bc%89-6/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%a2%a9%e9%96%80%ef%bc%89-4/ + + +数据无效(2): 当前第 25 页: 第 49 个无效。与 匯成教育中心(IBEC), 葉小姐, 26777778/54119779, adminibec@biznetvigator.com, https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7/ + + +数据无效(2): 当前第 25 页: 第 51 个无效。与 Good Hope School 德望學校, Ms. Sonia Wong, 23210250(Ext.22), recruitment@ghs.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%9c%8b%e8%a1%93%e5%ad%b8%e6%9c%83%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%88%9e%e8%b9%88%e5%ad%b8%e6%9c%83%e5%b0%8e%e5%b8%ab-4/ + + +数据无效(2): 当前第 25 页: 第 56 个无效。与 基督教香港信義會元朗信義中學, 陳貴月小姐 (秘書), 24480622, tswyllss@yahoo.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e4%ba%94%e7%b4%9a%e8%8b%b1%e8%aa%9e%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b/ + + +数据无效(2): 当前第 25 页: 第 57 个无效。与 Man Kai Education, Miss Chan, 26688294, eunice@monkeymaster.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor/ + + +数据无效(2): 当前第 25 页: 第 58 个无效。与 Man Kai Education, Miss Chan, 26688294, eunice@monkeymaster.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%a8%e8%81%b7%e5%b0%8e%e5%b8%ab-full-time-tutor/ + + +数据无效(2): 当前第 25 页: 第 59 个无效。与 /, Mr. Chan / Ms. Wong, 97914544, popularlearningskw@gmail.com, https://recruit.hkfew.org.hk/jobs/%e6%95%99%e5%ae%a4%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e6%96%87-centre-tutor-chinese-%e5%85%bc%e8%81%b7-part-time-%e5%85%a8%e8%81%b7-full-time-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e6%88%96%e8%8b%b1%e6%96%87%e5%b0%88%e7%a7%91%e5%b0%8e%e5%b8%ab%e5%85%a8%e8%81%b7%e6%88%96%e5%85%bc%e8%81%b7/ + + +数据无效(2): 当前第 26 页: 第 61 个无效。与 香港四邑商工總會陳南昌紀念中學, 林小姐, 27410326, info@cnc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad-%e5%88%9d%e4%b8%ad%e6%95%b8%e5%ad%b8%e8%aa%b2%e5%be%8c%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 26 页: 第 62 个无效。与 Tom Lee Music Co. Ltd, Ophelia Choi, 27377530, hr@tomleemusic.com, https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-9/ + + +数据无效(2): 当前第 26 页: 第 63 个无效。与 香港四邑商工總會陳南昌紀念中學, 林小姐, 27410326, info@cnc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad-%e5%88%9d%e4%b8%ad%e8%8b%b1%e6%96%87%e8%aa%b2%e5%be%8c%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 26 页: 第 65 个无效。与 華英中學, 行政主任麥先生, 27607772, recruit@waying.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%88%9e%e8%b9%88%e5%9c%98%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 26 页: 第 66 个无效。与 Delia (Man Kiu) English Primary School, Ms Leung, 24325123, ty@deliagroup.edu.hk, https://recruit.hkfew.org.hk/jobs/after-school-chinese-tutorial-class-tutor-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/after-school-chinese-tutorial-class-tutor/ + + +数据无效(2): 当前第 26 页: 第 68 个无效。与 Bishop Hall Jubilee School, Ms. Wong, 23363034, hr@bhjs.edu.hk, https://recruit.hkfew.org.hk/jobs/tutors-for-enhancement-classes-after-school-classes-saturday-remedial-classes/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/part-time-instructors-and-coaches-for-2022-2023-school-year/ + + +数据无效(2): 当前第 26 页: 第 69 个无效。与 東華三院李潤田紀念中學, 戲劇組負責老師, 25715422, job@lcdmc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%8e%92%e7%90%83%e9%9a%8a%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 26 页: 第 70 个无效。与 東華三院李潤田紀念中學, 戲劇組負責老師, 25715422, job@lcdmc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e8%88%9e%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 26 页: 第 71 个无效。与 HKCCCU Logos Academy, Personnel Department, 23372123, jobs@logosacademy.edu.hk, https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%b6%e6%9c%8d%e5%9c%98%e9%ab%94%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 26 页: 第 73 个无效。与 香港教育工作者聯會黃楚標中學,陳小姐,21094005,contact@wcbss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b9%b3%e6%9d%bf%e9%9b%bb%e8%85%a6%e9%9b%bb%e7%b9%aa%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%9c%8b%e7%95%ab%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 26 页: 第 74 个无效。与 香港教育工作者聯會黃楚標中學,陳小姐,21094005,contact@wcbss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b9%b3%e6%9d%bf%e9%9b%bb%e8%85%a6%e9%9b%bb%e7%b9%aa%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%82%b3%e7%b5%b1%e5%b7%a5%e8%97%9d%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 26 页: 第 75 个无效。与 香港教育工作者聯會黃楚標中學,陳小姐,21094005,contact@wcbss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b9%b3%e6%9d%bf%e9%9b%bb%e8%85%a6%e9%9b%bb%e7%b9%aa%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e5%9c%8b%e6%9b%b8%e6%b3%95%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 26 页: 第 76 个无效。与 東華三院郭一葦中學, 莊小姐, 24471258, office@twghkywc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e8%aa%b2%e5%be%8c%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%81%98%e8%ab%8b%e9%ab%98%e4%b8%ad%e8%aa%b2%e5%be%8c%e6%95%b8%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 26 页: 第 77 个无效。与 金巴崙長老會耀道中學, 莊小姐, 24730777, info@cpcydss.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab%e8%8a%b1%e8%97%9d%e5%8f%8a%e9%a3%be%e5%93%81%e8%a3%bd%e4%bd%9c%e3%80%81%e9%ab%ae%e5%9e%8b%e8%a8%ad%e8%a8%88%e3%80%81%e6%94%9d%e5%bd%b1%e5%8f%8a%e5%89%aa/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/2022-23%e5%ad%b8%e5%b9%b4-%e8%aa%a0%e8%81%98%e4%bb%a5%e4%b8%8b%e8%81%b7%e4%bd%8d/ + + +数据无效(1): 当前第 26 页: 第 79 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%8a%e9%96%80%e6%95%99%e6%8e%88%e5%90%84%e9%a1%9e%e6%a8%82%e5%99%a8%e8%80%81%e5%b8%ab-2/ + + +数据无效(2): 当前第 27 页: 第 81 个无效。与 循道衛理楊震社會服務處 何文田青少年綜合發展中心,區本陳姑娘,27181330,heidychan@yang.org.hk,https://recruit.hkfew.org.hk/jobs/%e8%a8%98%e6%86%b6%e5%8a%9b%e5%b0%8e%e5%b8%ab%ef%bc%88%e8%81%b7%e4%bd%8d%e7%b7%a8%e8%99%9f%ef%bc%9ahic-cbp-m%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%89%8b%e5%b7%a5%e8%97%9d%e5%b0%8e%e5%b8%ab%ef%bc%88%e8%81%b7%e4%bd%8d%e7%b7%a8%e8%99%9f%ef%bc%9ahic-cbp-a%ef%bc%89/ + + +数据无效(2): 当前第 27 页: 第 82 个无效。与 循道衛理楊震社會服務處 何文田青少年綜合發展中心,區本陳姑娘,27181330,heidychan@yang.org.hk,https://recruit.hkfew.org.hk/jobs/%e8%a8%98%e6%86%b6%e5%8a%9b%e5%b0%8e%e5%b8%ab%ef%bc%88%e8%81%b7%e4%bd%8d%e7%b7%a8%e8%99%9f%ef%bc%9ahic-cbp-m%ef%bc%89/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab%ef%bc%88%e8%81%b7%e4%bd%8d%e7%b7%a8%e8%99%9f%ef%bc%9ahic-cbp-t%ef%bc%89/ + + +数据无效(2): 当前第 27 页: 第 83 个无效。与 協恩中學附屬小學, 蔡小姐, 27111263, hyps@hyps.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%85%bc%e8%81%b7/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%a0%e8%81%9822-23%e5%b9%b4%e5%ba%a6%e6%a8%b9%e8%84%82%e9%bb%8f%e5%9c%9f%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 27 页: 第 84 个无效。与 協恩中學附屬小學, 蔡小姐, 27111263, hyps@hyps.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%85%bc%e8%81%b7/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%a0%e8%81%9822-23%e5%b9%b4%e5%ba%a6%e9%99%b6%e8%97%9d%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 27 页: 第 86 个无效。与 神召會康樂中學, 湯小姐, 26520698, application@gc.hebron.edu.hk, https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e6%95%b8%e5%ad%b8%e8%a3%9c%e7%bf%92%e7%8f%ad/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab-%e8%88%9e%e8%b9%88%e5%ad%b8%e6%9c%83/ + + +数据无效(2): 当前第 27 页: 第 89 个无效。与 明愛粉嶺陳震夏中學, 梁雅燕老師, 26699966, cfs@cfs.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab-%e5%88%9d%e4%b8%ad%e3%80%81%e9%ab%98%e4%b8%ad%e6%a0%b8%e5%bf%83%e7%a7%91%e5%8f%8a%e9%81%b8%e4%bf%ae%e7%a7%91-5%e4%bd%8d/ + + +数据无效(2): 当前第 27 页: 第 90 个无效。与 Tom Lee Music Co. Ltd, Ophelia Choi, 27377530, hr@tomleemusic.com, https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%89%8b%e9%88%b4%e8%80%81%e5%b8%ab-hand-chimes-tutor/ + + +数据无效(2): 当前第 27 页: 第 91 个无效。与 聖雅各福群會 延續教育中心, Daphne Suen, 28313268, daphne.suen@sjs.org.hk, https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8-%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%b0%88%e7%a7%91%e7%8f%ad%e5%b0%8e%e5%b8%ab%e7%b6%b2%e8%aa%b2%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e5%b0%88%e7%a7%91%e7%8f%ad%e8%8d%83%e7%81%a3%e3%80%81%e8%91%b5%e6%b6%8c%e5%8d%80-%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99%e5%b8%ab-5/ + + +数据无效(2): 当前第 27 页: 第 93 个无效。与 樂善堂余近卿中學, Ms. Yeung, 23362657, job@ykh.edu.hk, https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%85%ad%e7%b4%9a%e4%b8%ad%e5%9c%8b%e8%aa%9e%e6%96%87%e3%80%81%e8%8b%b1%e5%9c%8b%e8%aa%9e%e6%96%87%e5%8f%8a%e6%95%b8%e5%ad%b8%e7%a7%91%e7%b2%be%e9%80%b2%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 27 页: 第 96 个无效。与 香港宣教會粉嶺家庭中心,陳先生,26764010,gordon_chan@hkecffc.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e8%b7%86%e6%8b%b3%e9%81%93%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e9%ab%94%e6%93%8d%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 27 页: 第 98 个无效。与 順德聯誼總會翁祐中學, 姚小姐, 31570632, info@stfa-yyc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e7%94%9f%e7%89%a9%e7%a7%91%e7%a7%91%e5%89%b5%e8%a8%93%e7%b7%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 27 页: 第 99 个无效。与 順德聯誼總會翁祐中學, 姚小姐, 31570632, info@stfa-yyc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e7%a7%91%e5%89%b5%e8%a8%93%e7%b7%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 27 页: 第 100 个无效。与 順德聯誼總會翁祐中學, 姚小姐, 31570632, info@stfa-yyc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e7%89%a9%e7%90%86%e7%a7%91%e7%a7%91%e5%89%b5%e8%a8%93%e7%b7%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 28 页: 第 101 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%e6%95%b8%e5%90%8d-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-6/ + + +数据无效(2): 当前第 28 页: 第 102 个无效。与 奇趣學藝坊有限公司, 薜小姐, 25200353, info.school.hkclp@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%b0%87%e8%bb%8d%e6%be%b3%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad-%ef%bc%88%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%89%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 28 页: 第 104 个无效。与 聖雅各福群會 延續教育中心, Daphne Suen, 28313268, daphne.suen@sjs.org.hk, https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8-%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%b0%88%e7%a7%91%e7%8f%ad%e5%b0%8e%e5%b8%ab%e7%b6%b2%e8%aa%b2%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e8%8b%b1%e6%96%87%e5%b0%88%e7%a7%91%e7%8f%ad%e8%8d%83%e7%81%a3%e3%80%81%e8%91%b5%e6%b6%8c%e5%8d%80-%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99%e5%b8%ab-4/ + + +数据无效(2): 当前第 28 页: 第 105 个无效。与 大衆教室佐敦分校(恒豐中心), 李小姐, 94162842, sinotrend13@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-8/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 28 页: 第 107 个无效。与 Steamlabo Education Centre,China Chung,98210908,hr@steamlabo.hk,https://recruit.hkfew.org.hk/jobs/tseung-kwan-o-full-time-teacher-kindergarten-to-junior-school/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/tseung-kwan-o-steamlabo-education-part-time-playgroup-teacher-online-class/ + + +数据无效(2): 当前第 28 页: 第 108 个无效。与 Steamlabo Education Centre,China Chung,98210908,hr@steamlabo.hk,https://recruit.hkfew.org.hk/jobs/tseung-kwan-o-full-time-teacher-kindergarten-to-junior-school/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/tseung-kwan-o-steamlabo-education-part-time-homework-guidance-tutor%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e8%80%81%e5%b8%ab/ + + +数据无效(2): 当前第 28 页: 第 109 个无效。与 聖士提反女子中學, Ms. Josephine Yuen, 25492521, ssgcpost@ssgc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/biology-tutor-part-time/ + + +数据无效(2): 当前第 28 页: 第 110 个无效。与 華英中學, 行政主任麥先生, 27607772, recruit@waying.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/english-tutor-for-s-6-students/ + + +数据无效(2): 当前第 28 页: 第 111 个无效。与 御學軒集團有限公司, Enos Cheng, 63604267, hr@royallearning.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-playgroup%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 28 页: 第 112 个无效。与 天主教培聖中學, 宋小姐, 24450800, info@puishing.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%89%af%e6%95%99%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7%ef%bc%89%ef%bc%8f%e9%ab%98%e4%b8%ad%e4%b8%ad%e6%96%87%e5%a2%9e%e6%bd%a4%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%bc/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e8%aa%9e%e9%9f%b3%e6%a8%82%e5%8a%87%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 28 页: 第 113 个无效。与 順德聯誼總會翁祐中學, 姚小姐, 31570632, info@stfa-yyc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e8%be%af%e8%ab%96%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 28 页: 第 114 个无效。与 順德聯誼總會翁祐中學, 姚小姐, 31570632, info@stfa-yyc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%b7%b3%e9%ab%98%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 28 页: 第 115 个无效。与 進研學習中心, 楊小姐, 67909241, infocontemporaryedu@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%a0%85%e5%b0%bc%e5%9c%b0%e5%9f%8e%ef%bc%89/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%a0%85%e5%b0%bc%e5%9c%b0%e5%9f%8e%ef%bc%89/ + + +数据无效(1): 当前第 28 页: 第 117 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%b1%8e%e7%90%b4%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 28 页: 第 118 个无效。与 新會商會陳白沙紀念中學, 李穎沁老師, 25535324, hr@cpss.edu.hk, https://recruit.hkfew.org.hk/jobs/2022-2023%e5%ad%b8%e5%b9%b4%e7%94%b7%e7%ab%a5%e8%bb%8d%e5%90%88%e7%b4%84%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e5%be%8c%e7%83%b9%e9%a3%aa%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 28 页: 第 119 个无效。与 Tom Lee Music Co. Ltd, Ophelia Choi, 27377530, hr@tomleemusic.com, https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%89%8b%e9%88%b4%e5%b0%8e%e5%b8%ab-hand-chimes-tutor/ + + +数据无效(1): 当前第 29 页: 第 121 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%88%e7%b4%84%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-2022%e5%b9%b49%e6%9c%88%e5%88%b0%e8%81%b7/ + + +数据无效(2): 当前第 29 页: 第 124 个无效。与 HKTA香港導師會, 李小姐, 21142188, april.lee@hkta.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%a4%a9%e6%b0%b4%e5%9c%8d%e5%8d%80%e4%b8%ad%e5%ad%b8%e4%b8%ad%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9d%92%e8%a1%a3%e5%8d%80%e4%b8%ad%e4%ba%94-%e4%b8%ad%e5%85%ad%e8%8b%b1%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 29 页: 第 127 个无效。与 齊齊學教育中心(佐敦), 中心主任劉女士, 23779039, info@cometolearn.net, https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b0%8f%e5%ad%b8%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%e4%bd%90%e6%95%a6%ef%bc%9b14k-16k-month/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8f%e5%ad%b8%e8%a3%9c%e7%bf%92%e5%8a%a9%e7%90%86/ + + +数据无效(2): 当前第 29 页: 第 129 个无效。与 /,陳小姐,26328953,baptistec@hkstbc.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-8/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e4%b8%bb%e4%bb%bb/ + + +数据无效(2): 当前第 29 页: 第 131 个无效。与 華英中學, 行政主任麥先生, 27607772, recruit@waying.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/2022-2023%e5%ad%b8%e5%b9%b4%e8%81%b2%e6%a8%82%e6%8a%80%e5%b7%a7%e6%8f%90%e5%8d%87%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 29 页: 第 132 个无效。与 華英中學, 行政主任麥先生, 27607772, recruit@waying.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/2022-2023%e5%ad%b8%e5%b9%b4%e5%90%84%e9%a1%9e%e8%aa%b2%e5%be%8c%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 29 页: 第 133 个无效。与 華英中學, 行政主任麥先生, 27607772, recruit@waying.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/2022-2023%e5%ad%b8%e5%b9%b4%e7%ae%a1%e6%a8%82%e5%9c%98%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 29 页: 第 134 个无效。与 保良局陸慶濤小學, 藍小姐, 27010011, cmlam@plklht.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%90%88%e5%94%b1%e5%9c%98%e6%8c%87%e6%8f%ae%e4%bb%a3%e8%aa%b2-29-11-28-2%e6%9c%9f%e9%96%93/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%99%ae%e9%80%9a%e8%a9%b1%e9%9b%86%e8%aa%a6%e9%9a%8a%e5%b0%8e%e5%b8%ab%e5%8f%8a%e5%ad%b8%e7%94%9f%e6%bc%94%e8%ac%9b%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 29 页: 第 135 个无效。与 天主教培聖中學, 宋小姐, 24450800, info@puishing.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%89%af%e6%95%99%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7%ef%bc%89%ef%bc%8f%e9%ab%98%e4%b8%ad%e4%b8%ad%e6%96%87%e5%a2%9e%e6%bd%a4%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%bc/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e9%9d%9e%e8%8f%af%e8%aa%9e%e4%b8%ad%e6%96%87%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-3/ + + +数据无效(2): 当前第 29 页: 第 136 个无效。与 香港戒毒會 凹頭青少年中心, 梁生, 24787026, sswa_atyc@sarda.org.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%bf%83%e5%85%a8%e8%81%b7%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-full-time-chinese-english-mathematics-teacher/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%bf%83%e5%85%a8%e8%81%b7%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-%e6%95%b8%e5%90%8d/ + + +数据无效(2): 当前第 29 页: 第 138 个无效。与 華英中學, 行政主任麥先生, 27607772, recruit@waying.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/2022-2023%e5%ad%b8%e5%b9%b4%e5%bc%a6%e6%a8%82%e5%9c%98%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 29 页: 第 139 个无效。与 華英中學, 行政主任麥先生, 27607772, recruit@waying.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/2022-2023%e5%ad%b8%e5%b9%b4%e7%ac%9b%e9%9a%8a%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 29 页: 第 140 个无效。与 保良局陸慶濤小學, 藍小姐, 27010011, cmlam@plklht.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%90%88%e5%94%b1%e5%9c%98%e6%8c%87%e6%8f%ae%e4%bb%a3%e8%aa%b2-29-11-28-2%e6%9c%9f%e9%96%93/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%99%ae%e9%80%9a%e8%a9%b1%e9%9b%86%e8%aa%a6%e9%9a%8a%e5%b0%8e%e5%b8%ab%e5%8f%8a%e5%ad%b8%e7%94%9f%e6%bc%94%e8%ac%9b%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 142 个无效。与 東華三院馬振玉紀念中學,呂君豪老師,24439833,luikwanho@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%94%80%e6%a8%b9%e5%8f%8a%e6%ad%b7%e5%a5%87%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%a3%8b%e8%97%9d%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 144 个无效。与 基督教宣道會華基堂青年中心,張姑娘,25380644,cheunghy@cmawkc.org,https://recruit.hkfew.org.hk/jobs/%e8%8a%b1%e5%bc%8f%e8%b7%b3%e7%b9%a9%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%92%e7%ab%a5%e8%b7%86%e6%8b%b3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 145 个无效。与 明愛樂恩學校,柳小姐,23100440,info@cmts.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e8%88%9e%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8f%a4%e5%85%b8%e8%81%b2%e6%a8%82%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 146 个无效。与 明愛樂恩學校,柳小姐,23100440,info@cmts.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e8%88%9e%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e7%88%b5%e5%a3%ab%e9%bc%93%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 147 个无效。与 明愛樂恩學校,柳小姐,23100440,info@cmts.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e8%88%9e%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%8b%89%e7%ad%8b%e4%bc%b8%e5%b1%95%e7%91%9c%e7%8f%88%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 148 个无效。与 明愛樂恩學校,柳小姐,23100440,info@cmts.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e8%88%9e%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 150 个无效。与 華英中學, 行政主任麥先生, 27607772, recruit@waying.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%be%af%e8%ab%96%e9%9a%8a%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 30 页: 第 151 个无效。与 Tom Lee Music Co. Ltd, Ophelia Choi, 27377530, hr@tomleemusic.com, https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-8/ + + +数据无效(2): 当前第 30 页: 第 153 个无效。与 康盈中英文幼稚園, 盧承依校長, 27723163, hongyinginfo@gmail.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-15/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-11/ + + +数据无效(2): 当前第 30 页: 第 154 个无效。与 HKTA香港導師會, 劉小姐, 55961800, sandi.lau@hkta.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%ad%b8%e8%8b%b1%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(1): 当前第 30 页: 第 155 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/full-time-part-time-tutor/ + + +数据无效(2): 当前第 30 页: 第 156 个无效。与 HKTA香港導師會, 李小姐, 21142188, april.lee@hkta.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%a4%a9%e6%b0%b4%e5%9c%8d%e5%8d%80%e4%b8%ad%e5%ad%b8%e4%b8%ad%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8d%83%e7%81%a3%e5%8d%80%e5%b0%8f%e5%ad%b8%e9%81%8a%e6%88%b2%e6%b2%bb%e7%99%82%e5%b0%8f%e7%b5%84%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 157 个无效。与 HKTA香港導師會, 李小姐, 21142188, april.lee@hkta.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%a4%a9%e6%b0%b4%e5%9c%8d%e5%8d%80%e4%b8%ad%e5%ad%b8%e4%b8%ad%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9d%92%e8%a1%a3%e5%8d%80%e4%b8%ad%e4%ba%94-%e4%b8%ad%e5%85%ad%e8%8b%b1%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 159 个无效。与 香港四邑商工總會陳南昌紀念中學, 林小姐, 27410326, info@cnc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e8%aa%b2%e5%be%8c%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 30 页: 第 160 个无效。与 機構 Kids' Gallery Company Limited, 潘小姐, 23371001, careers@kggeducation.edu.hk, https://recruit.hkfew.org.hk/jobs/drama-teacher-pt-worked-on-saturday/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/teacher-dance-art/ + + +数据无效(2): 当前第 31 页: 第 161 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%e6%95%b8%e5%90%8d-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-5/ + + +数据无效(2): 当前第 31 页: 第 162 个无效。与 御學軒集團有限公司, Enos Cheng, 63604267, hr@royallearning.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 31 页: 第 163 个无效。与 御學軒集團有限公司, Enos Cheng, 63604267, hr@royallearning.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e6%95%b8%e5%ad%b8%e5%b0%88%e7%a7%91%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 31 页: 第 164 个无效。与 御學軒集團有限公司, Enos Cheng, 63604267, hr@royallearning.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 31 页: 第 165 个无效。与 御學軒集團有限公司, Enos Cheng, 63604267, hr@royallearning.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%f0%9f%91%a9%f0%9f%8f%bb%e2%80%8d%f0%9f%8f%ab%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab-%e6%b2%b9%e9%ba%bb%e5%9c%b0/ + + +数据无效(2): 当前第 31 页: 第 167 个无效。与 東華三院馬振玉紀念中學,謝爾溱老師,24439833,tseyitsun@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8f%a4%e7%ae%8f%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%88%e5%94%b1%e5%9c%98%e4%bc%b4%e5%a5%8f%e5%b0%8e%e5%b8%ab1%e5%90%8d/ + + +数据无效(2): 当前第 31 页: 第 168 个无效。与 東華三院馬振玉紀念中學,謝爾溱老師,24439833,tseyitsun@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8f%a4%e7%ae%8f%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e9%9f%bf%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ + + +数据无效(2): 当前第 31 页: 第 169 个无效。与 東華三院馬振玉紀念中學,謝爾溱老師,24439833,tseyitsun@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8f%a4%e7%ae%8f%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e7%a2%bc%e9%8b%bc%e7%90%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ + + +数据无效(2): 当前第 31 页: 第 170 个无效。与 東華三院馬振玉紀念中學,謝爾溱老師,24439833,tseyitsun@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8f%a4%e7%ae%8f%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e7%84%a1%e4%bc%b4%e5%a5%8f%e5%90%88%e5%94%b1%e5%9c%98%e5%b0%8e%e5%b8%ab1%e5%90%8d/ + + +数据无效(2): 当前第 31 页: 第 171 个无效。与 東華三院馬振玉紀念中學,謝爾溱老師,24439833,tseyitsun@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8f%a4%e7%ae%8f%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%89%8b%e9%90%98%e9%9a%8a%e5%b0%8e%e5%b8%ab1%e5%90%8d/ + + +数据无效(2): 当前第 31 页: 第 172 个无效。与 東華三院馬振玉紀念中學,謝爾溱老師,24439833,tseyitsun@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8f%a4%e7%ae%8f%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e7%b5%90%e4%bb%96%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ + + +数据无效(2): 当前第 31 页: 第 174 个无效。与 同學坊教育中心(旺角),MS Winni Tong,27826922,mongkok@edfun.com.hk,https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 31 页: 第 175 个无效。与 香港紅十字會雅麗珊郡主學校, 劉小姐, 23401022, pars@redcross.org.hk, https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%a7%91%e8%aa%b2%e5%be%8c%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%a8%82%e5%99%a8%e7%8f%ad%e5%b0%8e%e5%b8%ab-4/ + + +数据无效(2): 当前第 31 页: 第 177 个无效。与 文理書院(香港), 校務處, 25567413, cognitiohk@cognitiohk.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9b%9b%e7%b4%9a%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%b2%b5%e8%aa%9e%e6%9c%97%e8%aa%a6%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 31 页: 第 178 个无效。与 匯成教育中心(IBEC), 葉小姐, 26777778/54119779, adminibec@biznetvigator.com, https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%8d%8a%e8%81%b7%ef%bc%89/ + + +数据无效(2): 当前第 31 页: 第 180 个无效。与 鄰舍輔導會 賽馬會天水圍綜合服務中心, 嚴姑娘, 26178816, tssc@naac.org.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-9/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-7/ + + +数据无效(2): 当前第 32 页: 第 181 个无效。与 鄰舍輔導會 賽馬會天水圍綜合服務中心, 嚴姑娘, 26178816, tssc@naac.org.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-9/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-6/ + + +数据无效(2): 当前第 32 页: 第 182 个无效。与 梁式芝書院, 鄭小姐, 23496626, recruitment@lscc.edu.hk, https://recruit.hkfew.org.hk/jobs/enhancement-class-tutors-for-students-after-school/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8f%a4%e7%ae%8f%e9%9a%8a%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 32 页: 第 183 个无效。与 The Hong Kong Council of the Church of Christ in China 中華基督教會香港區會家情軒, 丁小姐 / 莊小姐, 24414833, fwinfo@hkcccc.org, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0-5/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0/ + + +数据无效(2): 当前第 32 页: 第 184 个无效。与 文理書院(香港), 校務處, 25567413, cognitiohk@cognitiohk.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9b%9b%e7%b4%9a%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%a8%82%e5%99%a8%e5%8f%8a%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 32 页: 第 186 个无效。与 JEI Diversity Learning Centre(Tsing Yi), Miss Chow, 60128411, naturejovial@gmail.com, https://recruit.hkfew.org.hk/jobs/%e6%9c%ac%e5%9c%b0%e6%95%b8%e5%ad%b8%e6%88%96%e8%8b%b1%e6%96%87%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%9c%ac%e5%9c%b0%e6%95%b8%e5%ad%b8%e6%88%96%e8%8b%b1%e6%96%87%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 32 页: 第 188 个无效。与 Steamlabo Education Centre,China Chung,98210908,hr@steamlabo.hk,https://recruit.hkfew.org.hk/jobs/tseung-kwan-o-full-time-teacher-kindergarten-to-junior-school/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/tseung-kwan-o-steamlabo-education-pt-playgroup-teacher-%e6%ad%a1%e8%bf%8e%e6%9c%89%e7%b6%93%e9%a9%97%e8%80%81%e5%b8%ab%e5%8a%a0%e5%85%a5%e5%9c%98%e9%9a%8a/ + + +数据无效(2): 当前第 32 页: 第 192 个无效。与 聖雅各福群會 延續教育中心, Daphne Suen, 28313268, daphne.suen@sjs.org.hk, https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8-%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%b0%88%e7%a7%91%e7%8f%ad%e5%b0%8e%e5%b8%ab%e7%b6%b2%e8%aa%b2%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e3%80%8e%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e3%80%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%e5%8d%80-%e4%b8%80%e8%87%b3%e4%ba%94-%e5%b0%8e-3/ + + +数据无效(2): 当前第 32 页: 第 193 个无效。与 Tom Lee Music Co. Ltd, Ophelia Choi, 27377530, hr@tomleemusic.com, https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-7/ + + +数据无效(2): 当前第 32 页: 第 195 个无效。与 香港真光書院, 莫潔儀老師, 28711214, hktlcoff@hkstar.com, https://recruit.hkfew.org.hk/jobs/%e8%88%9e%e5%8f%b0%e9%9f%b3%e9%9f%bf%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%9b%95%e5%a1%91%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 32 页: 第 196 个无效。与 HKTA香港導師會, 李小姐, 21142188, april.lee@hkta.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%a4%a9%e6%b0%b4%e5%9c%8d%e5%8d%80%e4%b8%ad%e5%ad%b8%e4%b8%ad%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%91%b5%e6%b6%8c%e5%8d%80%e4%b8%ad%e4%b8%80%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 32 页: 第 197 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%e6%95%b8%e5%90%8d-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + + +数据无效(1): 当前第 32 页: 第 198 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e6%95%99%e5%ae%a4%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%ef%bc%89-2/ + + +数据无效(2): 当前第 32 页: 第 199 个无效。与 浸信會沙田教育中心, 陳小姐, 82003351, baptistec@hkstbc.org, https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%85%bc%e8%81%b7-%e6%b2%99%e7%94%b0%e5%8d%80/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e8%aa%b2%e5%be%8c%e5%b0%8e%e5%b8%ab-3/ + + +数据无效(2): 当前第 32 页: 第 200 个无效。与 東華三院邱金元中學, 楊先生 / 吳小姐, 26497385, recruit@twyky.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%a7%91%e6%96%87%e6%86%91%e8%a9%a6%e6%8b%94%e5%b0%96%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e5%9b%9b%e3%80%81%e4%b8%ad%e4%ba%94%e7%b4%9a/ + + +数据无效(2): 当前第 33 页: 第 201 个无效。与 東華三院邱金元中學, 楊先生 / 吳小姐, 26497385, recruit@twyky.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e7%a7%91%e6%96%87%e6%86%91%e8%a9%a6%e6%8b%94%e5%b0%96%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e5%9b%9b%e3%80%81%e4%b8%ad%e4%ba%94%e7%b4%9a/ + + +数据无效(2): 当前第 33 页: 第 202 个无效。与 東華三院邱金元中學, 楊先生 / 吳小姐, 26497385, recruit@twyky.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e6%96%87%e6%86%91%e8%a9%a6%e6%8b%94%e5%b0%96%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e5%9b%9b%e3%80%81%e4%b8%ad%e4%ba%94%e7%b4%9a/ + + +数据无效(2): 当前第 33 页: 第 203 个无效。与 聖公會林裘謀中學, 郭小姐, 26488222, skhlkmss@skhlkmss.edu.hk, https://recruit.hkfew.org.hk/jobs/coaches-for-english-public-speaking-solo-verse-speaking/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/music-instructor-3/ + + +数据无效(2): 当前第 33 页: 第 204 个无效。与 香港道教聯合會圓玄幼稚園, 招校長, 24984636, hktayykg@yuenyuenkg.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%8d%8a%e6%97%a5%e9%9d%9e%e8%8f%af%e8%aa%9e%e5%ad%b8%e7%ab%a5%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab-half-day-teacher-supporting-non-chinese-speaking-students-ncs-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%8d%8a%e6%97%a5%e9%9d%9e%e8%8f%af%e8%aa%9e%e5%ad%b8%e7%ab%a5%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab-half-day-teacher-supporting-non-chinese-speaking-students-ncs/ + + +数据无效(2): 当前第 33 页: 第 208 个无效。与 慈幼英文學校,葉小姐,28843581,admin@ssshk.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab-%e5%85%a8%e5%b9%b4%e7%89%b9%e5%ae%9a%e6%97%a5%e5%ad%90-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e3%80%8c%e8%8b%b1%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e6%a0%a1%e6%9c%ac%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e8%81%b7%e6%a5%ad%e8%8b%b1%e8%aa%9e%e8%aa%b2%e7%a8%8b%e3%80%8d/ + + +数据无效(2): 当前第 33 页: 第 209 个无效。与 慈幼英文學校,葉小姐,28843581,admin@ssshk.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab-%e5%85%a8%e5%b9%b4%e7%89%b9%e5%ae%9a%e6%97%a5%e5%ad%90-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e3%80%8c%e8%8b%b1%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e6%a0%a1%e6%9c%ac%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e8%aa%aa%e8%a9%b1%e5%8f%8a%e8%be%af%e8%ab%96%e8%aa%b2%e7%a8%8b%e3%80%8d/ + + +数据无效(2): 当前第 33 页: 第 215 个无效。与 慈幼英文學校,葉小姐,28843581,admin@ssshk.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab-%e5%85%a8%e5%b9%b4%e7%89%b9%e5%ae%9a%e6%97%a5%e5%ad%90-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e4%b8%80%e8%aa%9e%e6%96%87%e5%8a%a0%e5%bc%b7%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87/ + + +数据无效(2): 当前第 33 页: 第 216 个无效。与 慈幼英文學校,葉小姐,28843581,admin@ssshk.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab-%e5%85%a8%e5%b9%b4%e7%89%b9%e5%ae%9a%e6%97%a5%e5%ad%90-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e6%a0%a1%e6%9c%ac%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e9%96%b1%e8%ae%80%e8%aa%b2%e7%a8%8b/ + + +数据无效(2): 当前第 33 页: 第 217 个无效。与 機構 Kids' Gallery Company Limited, 潘小姐, 23371001, careers@kggeducation.edu.hk, https://recruit.hkfew.org.hk/jobs/drama-teacher-pt-worked-on-saturday/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/teacher-visual-arts/ + + +数据无效(2): 当前第 33 页: 第 218 个无效。与 Tom Lee Music Co. Ltd, Ophelia Choi, 27377530, hr@tomleemusic.com, https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-6/ + + +数据无效(2): 当前第 33 页: 第 219 个无效。与 機構 Kids' Gallery Company Limited, 潘小姐, 23371001, careers@kggeducation.edu.hk, https://recruit.hkfew.org.hk/jobs/drama-teacher-pt-worked-on-saturday/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/teacher-musical-theatre/ + + +数据无效(2): 当前第 34 页: 第 223 个无效。与 聖芳濟各書院, 李小姐, 26779709, principal@sfac.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%aa%a0%e8%81%98%e4%b8%ad%e6%96%87%e7%a7%91%e8%aa%9e%e6%96%87%e6%8a%80%e5%b7%a7%e7%8f%ad%e5%b0%8e%e5%b8%ab2%e5%90%8d/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e8%aa%9e%e6%96%87%e6%8a%80%e5%b7%a7%e7%8f%ad%e5%b0%8e%e5%b8%ab%e8%8b%b1%e6%96%87%e7%a7%91/ + + +数据无效(2): 当前第 34 页: 第 225 个无效。与 爾雅教育, 何小姐, 56012170, eryaeducation2021@gmail.com, https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e4%b8%ad%e5%bf%83%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e4%b8%ad%e5%bf%83%e6%95%b8%e7%90%86%e7%a7%91%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 34 页: 第 226 个无效。与 香港真光書院, 莫潔儀老師, 28711214, hktlcoff@hkstar.com, https://recruit.hkfew.org.hk/jobs/%e8%88%9e%e5%8f%b0%e9%9f%b3%e9%9f%bf%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e7%b6%93%e6%bf%9f%e7%a7%91%e7%b6%b2%e4%b8%8a%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 34 页: 第 228 个无效。与 御學軒集團有限公司, Enos Cheng, 63604267, hr@royallearning.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 34 页: 第 229 个无效。与 Good Hope School 德望學校, Ms. Sonia Wong, 23210250(Ext.22), recruitment@ghs.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%9c%8b%e8%a1%93%e5%ad%b8%e6%9c%83%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/part-time-workshop-instructor-woodwork-%e5%85%bc%e8%81%b7%e5%b7%a5%e5%a0%b4%e5%b0%8e%e5%b8%ab-%e6%9c%a8%e5%b7%a5/ + + +数据无效(2): 当前第 34 页: 第 231 个无效。与 The Hong Kong Council of the Church of Christ in China 中華基督教會香港區會家情軒, 丁小姐 / 莊小姐, 24414833, fwinfo@hkcccc.org, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0-5/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 34 页: 第 232 个无效。与 中華基督教會燕京書院, 伍小姐, 23879988, recruit@yenching.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%81%af%e8%aa%b2%e6%b4%bb%e5%8b%95%e5%8a%a9%e7%90%86%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%90%84%e8%81%af%e8%aa%b2%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%e9%ab%94%e8%82%b2-%e8%97%9d%e8%a1%93-%e8%88%88%e8%b6%a3%e5%b0%8f%e7%b5%84/ + + +数据无效(2): 当前第 34 页: 第 233 个无效。与 東華三院邱金元中學, 楊先生 / 吳小姐, 26497385, recruit@twyky.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e7%a7%91%e6%96%87%e6%86%91%e8%a9%a6%e6%8f%90%e6%98%87%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e5%85%ad%e7%b4%9a/ + + +数据无效(2): 当前第 34 页: 第 236 个无效。与 東華三院邱金元中學, 楊先生 / 吳小姐, 26497385, recruit@twyky.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e7%a7%91%e6%96%87%e6%86%91%e8%a9%a6%e6%8b%94%e5%b0%96%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e5%85%ad%e7%b4%9a/ + + +数据无效(2): 当前第 34 页: 第 237 个无效。与 Tom Lee Music Co. Ltd, Ophelia Choi, 27377530, hr@tomleemusic.com, https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-5/ + + +数据无效(2): 当前第 34 页: 第 238 个无效。与 Good Hope School 德望學校, Ms. Sonia Wong, 23210250(Ext.22), recruitment@ghs.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%9c%8b%e8%a1%93%e5%ad%b8%e6%9c%83%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/part-time-instrumental-instructor-piano-ensemble/ + + +数据无效(2): 当前第 34 页: 第 239 个无效。与 Good Hope School 德望學校, Ms. Sonia Wong, 23210250(Ext.22), recruitment@ghs.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%9c%8b%e8%a1%93%e5%ad%b8%e6%9c%83%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/part-time-workshop-instructor-3d-modelling-and-design/ + + +数据无效(2): 当前第 34 页: 第 240 个无效。与 Good Hope School 德望學校, Ms. Sonia Wong, 23210250(Ext.22), recruitment@ghs.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%9c%8b%e8%a1%93%e5%ad%b8%e6%9c%83%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/part-time-workshop-instructor-iot/ + + +数据无效(2): 当前第 35 页: 第 242 个无效。与 Joyful art centre,Miss wong,60265103,joyfulart777@yahoo.com.hi,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a6%96%e8%a6%ba%e8%97%9d%e8%a1%93%e5%b0%8e%e5%b8%ab-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a6%96%e8%a6%ba%e8%97%9d%e8%a1%93%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 35 页: 第 243 个无效。与 小童星訓練學院,林小姐,93649364,smallmo@momocastingtalentschool.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%80%81%e5%b8%ab%ef%bc%8f%e5%b0%8e%e5%b8%ab%ef%bc%88%e6%8f%90%e5%8d%87%e5%ad%a9%e5%ad%90%e8%87%aa%e4%bf%a1%e5%bf%83%e7%9a%84%e8%aa%b2%e7%a8%8b%ef%bc%89-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%80%81%e5%b8%ab%ef%bc%8f%e5%b0%8e%e5%b8%ab%ef%bc%88%e6%8f%90%e5%8d%87%e5%ad%a9%e5%ad%90%e8%87%aa%e4%bf%a1%e5%bf%83%e7%9a%84%e8%aa%b2%e7%a8%8b%ef%bc%89/ + + +数据无效(2): 当前第 35 页: 第 244 个无效。与 順德聯誼總會翁祐中學, 姚小姐, 31570632, info@stfa-yyc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e6%9a%91%e6%9c%9f%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 35 页: 第 246 个无效。与 東華三院郭一葦中學, 莊小姐, 24471258, office@twghkywc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e8%aa%b2%e5%be%8c%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e4%b8%ad%e3%80%81%e8%8b%b1%e3%80%81%e6%95%b8%e5%b0%8f%e7%b5%84%e5%b0%8e%e5%b8%ab/ + + +数据无效(1): 当前第 35 页: 第 247 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/art-teacher-%e5%85%92%e7%ab%a5%e6%99%9d%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 35 页: 第 248 个无效。与 HKCCCU Logos Academy, Personnel Department, 23372123, jobs@logosacademy.edu.hk, https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%b6%e4%bb%96%e5%ad%b8%e7%bf%92%e7%b6%93%e6%ad%b7%e5%b0%8e%e5%b8%ab-%e7%8f%be%e4%bb%a3%e8%88%9e-%e7%8f%be%e4%bb%a3%e7%88%b5%e5%a3%ab%e8%88%9e%e3%80%81%e5%a1%97%e9%b4%89-%e7%b9%aa%e7%95%ab/ + + +数据无效(2): 当前第 35 页: 第 249 个无效。与 御學軒集團有限公司, Enos Cheng, 63604267, hr@royallearning.com, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-playgroup%e5%b0%8e%e5%b8%ab/ + + +数据无效(1): 当前第 35 页: 第 250 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%a4%a9%e6%b0%b4%e5%9c%8d%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e7%8f%ad%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab/ + + +数据无效(1): 当前第 35 页: 第 252 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-4/ + + +数据无效(2): 当前第 35 页: 第 253 个无效。与 浸信會沙田教育中心, 陳小姐, 82003351, baptistec@hkstbc.org, https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%85%bc%e8%81%b7-%e6%b2%99%e7%94%b0%e5%8d%80/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e8%aa%b2%e5%be%8c%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 35 页: 第 254 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%e6%95%b8%e5%90%8d-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-3/ + + +数据无效(2): 当前第 35 页: 第 255 个无效。与 路德會新翠少年中心,傅生,26912102,chorfung.fu@hklss.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-7/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e6%89%98%e7%ae%a1%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 35 页: 第 256 个无效。与 東華三院馬振玉紀念中學,呂君豪老師,24439833,luikwanho@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%94%80%e6%a8%b9%e5%8f%8a%e6%ad%b7%e5%a5%87%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%a0%98%e8%a2%96%e8%a8%93%e7%b7%b4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%e4%b8%ad%e5%9b%9b%e3%80%81%e4%ba%94%e7%b4%9a/ + + +数据无效(2): 当前第 35 页: 第 258 个无效。与 St. Paul's Secondary School, Ms. Li, 25773836, spssmail@spss.edu.hk, https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e4%b8%ad%e6%96%87%e6%95%99%e5%ad%b8%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/coaches-and-course-tutors-service-agreement-based-3/ + + +数据无效(2): 当前第 35 页: 第 259 个无效。与 東華三院郭一葦中學, 莊小姐, 24471258, office@twghkywc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e8%aa%b2%e5%be%8c%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab-%e6%95%99%e7%b7%b4/ + + +数据无效(2): 当前第 35 页: 第 260 个无效。与 同學坊教育中心(旺角),MS Winni Tong,27826922,mongkok@edfun.com.hk,https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%8f%ad%e5%b0%8e%e5%b8%ab/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/full-time-tutor-2/ + + +数据无效(2): 当前第 36 页: 第 261 个无效。与 Tom Lee Music Co. Ltd, Ophelia Choi, 27377530, hr@tomleemusic.com, https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 36 页: 第 262 个无效。与 東華三院邱金元中學, 楊先生 / 吳小姐, 26497385, recruit@twyky.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 36 页: 第 263 个无效。与 基督教香港信義會元朗信義中學, 陳貴月小姐 (秘書), 24480622, tswyllss@yahoo.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/english-oral-tutor-net/ + + +数据无效(2): 当前第 36 页: 第 264 个无效。与 文理書院(香港), 校務處, 25567413, cognitiohk@cognitiohk.edu.hk, https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9b%9b%e7%b4%9a%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e5%8f%8a%e9%ab%98%e4%b8%ad%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 36 页: 第 265 个无效。与 東華三院邱金元中學, 楊先生 / 吳小姐, 26497385, recruit@twyky.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e4%b8%80%e6%96%b0%e7%94%9f%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 36 页: 第 266 个无效。与 東華三院馬振玉紀念中學, 鄭慶珠老師, 24439833, chenghingchu@cyma.edu.hk, https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%e6%95%b8%e5%90%8d-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-2/ + + +数据无效(2): 当前第 36 页: 第 267 个无效。与 基督教香港信義會元朗信義中學, 陳貴月小姐 (秘書), 24480622, tswyllss@yahoo.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%ba%b2%e9%81%bf%e7%9b%a4%e6%95%99%e7%b7%b4%e5%8f%8a%e5%b0%8f%e4%b8%91%e8%88%88%e8%b6%a3%e7%8f%ad%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 36 页: 第 268 个无效。与 基督教香港信義會元朗信義中學, 陳貴月小姐 (秘書), 24480622, tswyllss@yahoo.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/bafs-tutor-3/ + + +数据无效(2): 当前第 36 页: 第 269 个无效。与 基督教香港信義會元朗信義中學, 陳貴月小姐 (秘書), 24480622, tswyllss@yahoo.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/bafs-tutor-2/ + + +数据无效(2): 当前第 36 页: 第 270 个无效。与 基督教香港信義會元朗信義中學, 陳貴月小姐 (秘書), 24480622, tswyllss@yahoo.com.hk, https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/dse%e7%b6%93%e6%bf%9f%e7%a7%91%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 36 页: 第 271 个无效。与 聖公會林裘謀中學, 郭小姐, 26488222, skhlkmss@skhlkmss.edu.hk, https://recruit.hkfew.org.hk/jobs/coaches-for-english-public-speaking-solo-verse-speaking/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/music-instructor-2/ + + +数据无效(2): 当前第 36 页: 第 272 个无效。与 Good Hope School,Ms. Sonia Wong,23210250(ext.22),recruitment@ghs.edu.hk,https://recruit.hkfew.org.hk/jobs/athletics-sprinting-hurdling-coach-2/ 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/athletics-sprinting-hurdling-coach/ + + +数据无效(2): 当前第 36 页: 第 273 个无效。与 聖士提反女子中學, Ms. Josephine Yuen, 25492521, ssgcpost@ssgc.edu.hk, https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/part-time-guitar-instructor/ + + +数据无效(1): 当前第 36 页: 第 275 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(1): 当前第 36 页: 第 278 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e8%aa%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ + + +数据无效(1): 当前第 36 页: 第 279 个无效。没有有效的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e7%a4%be%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab/ + + +数据无效(2): 当前第 35 页: 第 1 个无效。与 Joyful art centre, Miss wong, 60265103, joyfulart777@yahoo.com.hi, https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a6%96%e8%a6%ba%e8%97%9d%e8%a1%93%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a6%96%e8%a6%ba%e8%97%9d%e8%a1%93%e5%b0%8e%e5%b8%ab-2/ + + +数据无效(2): 当前第 35 页: 第 2 个无效。与 Joyful art centre, Miss wong, 60265103, joyfulart777@yahoo.com.hi, https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a6%96%e8%a6%ba%e8%97%9d%e8%a1%93%e5%b0%8e%e5%b8%ab-2/ + 具有相同的 tel 或 email。 url: https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a6%96%e8%a6%ba%e8%97%9d%e8%a1%93%e5%b0%8e%e5%b8%ab/ + + diff --git a/qlOut/recruit_info.xls b/qlOut/recruit_info.xls new file mode 100644 index 0000000..b058db6 --- /dev/null +++ b/qlOut/recruit_info.xls @@ -0,0 +1,295 @@ +17a06dfcea5294ee962ad6861399f235,香港教育服務中心,Erica Cheng,39924299,hr@hkescedu.com,https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e6%9d%b1%e6%b6%8c%e5%8d%80%e4%bb%a5%e9%9d%a2%e6%8e%88%e5%bd%a2%e5%bc%8f%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad/ +031d124e4e60ebd0edad692b55a03743,Mood Education (Prince Edward),Ms Chan,84904568,moodventure2022@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-3/ +5b90a2e55769c09bdc6df670a695ad0e,聖芳濟各書院,李小姐,26779709,principal@sfac.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%a0%e8%81%98%e4%b8%ad%e6%96%87%e7%a7%91%e8%aa%9e%e6%96%87%e6%8a%80%e5%b7%a7%e7%8f%ad%e5%b0%8e%e5%b8%ab2%e5%90%8d/ +2e66d9d5cf03f9e09b8a1c2be45c04fc,連青網絡-香港神託會,黃小姐,26371866,aster.wong@stewards.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%a2%a9%e9%96%80%ef%bc%89-6/ +22ec1a7ba99c891739fba9279a5681da,Parsons Music Limited,Reyes Cheng,23331863,tutor@parsonsmusic.com,https://recruit.hkfew.org.hk/jobs/%e5%85%92%e7%ab%a5%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-children-music-class-tutor/ +18f105b4d9504ca8e53507b6f5483c2f,聖雅各福群會 延續教育中心,Daphne Suen,59885027,daphne.suen@sjs.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e4%b8%ad%e6%96%87%e5%b0%88%e7%a7%91%e7%8f%ad%e8%8d%83%e7%81%a3%e3%80%81%e8%91%b5%e6%b6%8c%e5%8d%80-%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99%e5%b8%ab-6/ +2d2b74403569a83d2bf0fe8fa99bd978,爾雅教育,何小姐,56012170,eryaeducation2021@gmail.com,https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e4%b8%ad%e5%bf%83%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ +bdf23f0b82089eaca4b9d463d26b8177,香港傷殘青年協會,Anna SHAM,27596408,supemp@hkfhy.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e5%b0%8e%e5%b8%ab-%e5%83%b1%e5%93%a1%e5%86%8d%e5%9f%b9%e8%a8%93%e5%b1%80%e8%aa%b2%e7%a8%8b/ +642e5ed2d7b10da4a7c781cda429e0d0,阡陌中心,黎卓瑩女士,24332525,keiyan@ccbc.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%8d%8a%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab/ +88de9892a3caaf947fd795c1ac0e92b8,HKTA香港導師會,李小姐,21142188,april.lee@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%a4%a9%e6%b0%b4%e5%9c%8d%e5%8d%80%e4%b8%ad%e5%ad%b8%e4%b8%ad%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +e43d2975cb3eccd525830a36f272f3d6,勵致研習中心,黃生,61125961,fair_ltd@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-secondary-school-and-primary-school-tutor/ +dab83f59351312fd37b091333d127a85,大眾教室 (堅尼地城) Popular Learning Centre (Kennedy Town),Ms Lau,98073009,popularlearning.kt@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e6%95%99%e5%ae%a4%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8%ef%bc%89/ +0179595d2ce2971b13818150d8d6d475,創思教育管理有限公司,陳先生,64656677,recruit_1@ccon.com.hk,https://recruit.hkfew.org.hk/jobs/2022-2023%e5%b9%b4%e5%ba%a6%e5%90%84%e5%8d%80%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-part-time-school-tutor/ +aa8e178d5a87394c21198901e410d743,保良局何壽南教育服務中心,Ms Chan,23203244or23262627,hsnesc@plkhsn.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%88%e7%b4%84%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +897432e7458223d3eb117b134c8ef992,東華三院邱金元中學,楊先生 / 吳小姐,26497385,recruit@twyky.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%b6%b3%e7%90%83%e6%95%99%e7%b7%b4-%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95/ +2fcb0c36e76a0443ec245c5090aeebf2,豆豆導師招聘網,陳小姐,67480761,edu970330@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e8%a8%bb%e5%86%8a%e6%95%99%e5%b8%ab-%e7%a4%be%e5%b7%a5/ +6f2d52743459eb52c77cb06c6867d4af,Kinderland Playgroup and Learning Centre Limited,Miss kwan,95604448,kidskinderland@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e6%99%ae%e9%80%9a%e8%a9%b1%e5%b0%8e%e5%b8%ab/ +3e2704b0b8ced7d4535b102e8e3a5ec5,JEI Diversity Learning Centre(Tsing Yi),Miss Chow,60128411,naturejovial@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%9c%ac%e5%9c%b0%e6%95%b8%e5%ad%b8%e6%88%96%e8%8b%b1%e6%96%87%e5%b0%8e%e5%b8%ab-3/ +a91a9711eb774301fdc76038fb63c363,NEW ASIA,Miss Wong/ Mr. Tang,64322545,hkg.edu.hk@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e9%9f%b3%e6%a8%82%e7%8f%ad-%e8%97%9d%e8%a1%93%e7%8f%ad-%e9%81%8b%e5%8b%95%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e8%88%9e%e8%b9%88%e7%8f%ad%e5%b0%8e%e5%b8%ab-hip-hop-k-pop/ +3013d32a3903379486873b8ce221ea61,ENGLISH EXPRESS EDUCATION CENTRE,Miss Lam,54265083,tutor.com.hk@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%8a%e9%96%80%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e6%99%ae%e9%80%9a%e8%a9%b1/ +a23d91b8bcd26017e39a2ddbf86643f6,大衆教室(田心村),Ms Li,66501338,populartaiwai@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-6/ +a47f584c83670ffd6627babd1a09cdc4,Math Ocean Education Limited,Kelvin Tsui,26092333,reception@mathoceanhk.com,https://recruit.hkfew.org.hk/jobs/english-or-math-tutor-2/ +21129e7aa84dfb05438e0ee8f0ef7cdc,Magic Season Limited,Zoe Ng,61232292,magicseasonlimited@gmail.com,https://recruit.hkfew.org.hk/jobs/head-tutor-children-farming-learning-venue-full-time/ +eab05a3b6c0d1809f7451565a14135ef,MAMAGREENIA嬰幼教育園,馬小姐,51106339,info@mamagreenia.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e7%be%8e%e8%a1%93%e5%b0%8e%e5%b8%ab%e5%9b%9b%e5%a4%a9%e5%b7%a5%e4%bd%9c/ +bf5d5771b436019f192e6d24ef77bf8b,機構 Kids' Gallery Company Limited,潘小姐,23371001,careers@kggeducation.edu.hk,https://recruit.hkfew.org.hk/jobs/drama-teacher-pt-worked-on-saturday/ +5f118d93b531a6e3a7b575005e59c971,森林貓教育中心有限公司,江先生,46427095,kong@sss.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%e5%b0%8f%e5%ad%b8/ +47bb396ae3aeda7ffb44a852d274e863,基督教香港信義會元朗信義中學,陳貴月小姐 (秘書),24480622,tswyllss@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +35ca851a4bfcdc4ed1e45d25bd890377,活力教育,Ms Lau,55096636,energyeducationhk@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%b0%88%e7%a7%91%e7%8f%ad%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab/ +0c73fc19b12af377a3d87d13a81141fe,香港戒毒會 凹頭青少年中心,梁生,24787026,sswa_atyc@sarda.org.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%bf%83%e5%85%a8%e8%81%b7%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-full-time-chinese-english-mathematics-teacher/ +c1bfc1e3e986d64444a904c4ae5e011b,新會商會陳白沙紀念中學,李穎沁老師,25535324,hr@cpss.edu.hk,https://recruit.hkfew.org.hk/jobs/2022-2023%e5%ad%b8%e5%b9%b4%e7%94%b7%e7%ab%a5%e8%bb%8d%e5%90%88%e7%b4%84%e5%b0%8e%e5%b8%ab/ +8197b316f290ce8449d9b9efc339c3cf,大衆教室佐敦分校(恒豐中心),李小姐,94162842,sinotrend13@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-8/ +4cbd7633ffeea48ed041188c39013564,天主教培聖中學,宋小姐,24450800,info@puishing.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%89%af%e6%95%99%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7%ef%bc%89%ef%bc%8f%e9%ab%98%e4%b8%ad%e4%b8%ad%e6%96%87%e5%a2%9e%e6%bd%a4%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%bc/ +d9ae739262f22a7d738c02ca15c8b4c0,HKCCCU Logos Academy,Personnel Department,23372123,jobs@logosacademy.edu.hk,https://recruit.hkfew.org.hk/jobs/f-1-english-tutor/ +be9d7fa74dfb645f478b99307c12c905,奇趣學藝坊有限公司,薜小姐,25200353,info.school.hkclp@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%87%e8%bb%8d%e6%be%b3%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +fe87d578c1819fc3ac16ccf7a834f2b8,華英中學,行政主任麥先生,27607772,recruit@waying.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%bc%94%e5%b0%8e%e7%b5%84%e5%ad%b8%e8%a1%93%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +06d4189bef6dbaabfe02faea5fa7faa0,聖士提反女子中學,Ms. Josephine Yuen,25492521,ssgcpost@ssgc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%91%9c%e4%bc%bd%e6%95%99%e7%b7%b4-2/ +fa8c68062aeb078aa7399341ff7b1ddb,小領袖潛能發展教育中心,楊小姐,29479998,new.wonders@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%88%e7%b4%84%e5%88%b0%e6%a0%a1%ef%bc%88%e5%88%9d%e4%b8%ad%ef%bc%89%e5%a5%a7%e6%95%b8%e5%b0%8e%e5%b8%ab/ +99a1ea1045c728e16d21f9c37f6ce058,數研喜樂教育中心,陳先生,36197929,blissful@soinedu.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e6%95%b8%e5%ad%b8%e5%b0%8e%e5%b8%ab-maths-tutor/ +c30d8ddc95d48ded1693fc78cf9b02b6,文理書院(香港),校務處,25567413,cognitiohk@cognitiohk.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9b%9b%e7%b4%9a%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +49e5bc5176f50d3b7b63f1e2750d1cd1,Tom Lee Music Co. Ltd,Ophelia Choi,27377530,hr@tomleemusic.com,https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e5%b0%8e%e5%b8%ab-%e5%b9%bc%e5%85%92%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-%e7%b5%90%e4%bb%96%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4%e5%b0%8e%e5%b8%ab-14/ +6a731229321d6064caaa8e7333c1c492,東華三院李潤田紀念中學,戲劇組負責老師,25715422,job@lcdmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e8%a8%93%e7%b7%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +05952611f1c8896b3257a8c28940bc70,安基司國際幼兒園(滌濤山),周主任,26506660,cc@anchors.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab%e5%85%a8%e8%81%b7/ +1e9b053a564a30fc7e747e6b21358f7e,香港漢一語文研習學校,葉小姐,23848100,hklahkla@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e6%96%87%e5%b0%8e%e5%b8%ab%e9%80%a2%e6%98%9f%e6%9c%9f%e4%ba%94/ +9622dc42e38fc08781949fc5c1fa1f67,凱怡教育有限公司,梁小姐,35430309,hoikam00016@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +0bc755a440863f0010fbc3efdc5c0789,Unleashing Mind Professional Counselling Academy,MS CHEUNG,60765667,hr@dailyweb.hk,https://recruit.hkfew.org.hk/jobs/%e8%ae%80%e5%af%ab%e5%b0%8f%e7%b5%84%e5%b0%8e%e5%b8%ab/ +9e9c1868f021d11aa09e663eb8c19840,彧恩軒(中作教室),林老師,97017907,yuenxuan000@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e4%bd%9c%e6%96%87%e8%80%81%e5%b8%ab/ +e30e9339232d701d33b798b8bea17d51,小星光兒童教育中心,文先生,52169183,mylittlestar062020@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%ad%b8%e5%89%8d-%e5%b0%8f%e5%ad%b8%e7%a0%94%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7/ +dc2f2a2e8f129dda5b1e6f9abd96ac3d,東華三院馬振玉紀念中學,梁家熙老師,24439833,leungkaheisimon@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%85%ad%e6%96%87%e6%86%91%e8%a9%a6%e6%95%b8%e5%ad%b8%e6%94%af%e6%8f%b4%e8%aa%b2%e7%a8%8b-2/ +13827f6ec55f1c5e560565868d155d1f,東華三院馬振玉紀念中學,錢明榮老師,24439833,vincentmwchin@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%a7%91%e8%aa%b2%e5%be%8c%e8%a3%9c%e7%bf%92%e7%8f%ad/ +96141bae2f4b0b41d5e613762e394da7,東華三院馬振玉紀念中學,林慧聰老師,24439833,lamwaichung@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e4%ba%94%e7%b4%9a%e8%8b%b1%e6%96%87%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%e6%95%b8%e5%90%8d/ +088d0f8f2304ac39c541fcf8bb46c38d,樂善堂余近卿中學,Ms. Yeung,23362657,job@ykh.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%91%9c%e4%bc%bd%e5%b0%8e%e5%b8%ab/ +d4aa984c6ed05a88ef977f76911380bc,瑪利諾神父教會學校,傅先生,27775117,mfshr@mfs2.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e6%8f%90%e7%90%b4/ +b8861a5709023c08691a00027b0167da,孔聖堂中學,莫玉玲副校長,25763415,enquiry@chss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%80%a5%e8%81%98%e9%9d%9e%e8%8f%af%e8%aa%9e%e4%b8%ad%e6%96%87%e8%aa%b2%e5%be%8c%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab-3/ +e1d8c4c1adc892dbc0c4120bb3c7bad7,現代小學士有限公司,Miss Li,21421111,bachelor.partime@gmail.com,https://recruit.hkfew.org.hk/jobs/part-time-tutor-%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e5%b0%88%e7%a7%91%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%a4%a9%e6%b0%b4%e5%9c%8d/ +d954cec6d41ebcf8097eaa4933aba176,現代小學士有限公司,Miss Li,21421111,bachelor.fulltime@gmail.com,https://recruit.hkfew.org.hk/jobs/full-time-tutor-teacher-%e5%85%a8%e8%81%b7%e5%b0%8e%e5%b8%ab-%e9%a6%ac%e9%9e%8d%e5%b1%b1-%e5%a4%a7%e5%9f%94%e5%88%86%e6%a0%a1/ +5db6539f3c1433790fa82880ace40cf4,Modern Education (HK) Ltd,Queenie Ma,22563395,human.resources@modern.edu.hk,https://recruit.hkfew.org.hk/jobs/full-time-part-time-tutor-teacher-%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab/ +871100bae2447c5b24ab9cf2d95ef0c2,The Hong Kong Council of the Church of Christ in China 中華基督教會香港區會家情軒,丁小姐 / 莊小姐,24414833,fwinfo@hkcccc.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e4%bf%ae%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%b1%af%e9%96%80%ef%bc%8f%e5%a4%a9%e6%b0%b4%e5%9c%8d%ef%bc%8f%e5%85%83%e6%9c%97%ef%bc%8f%e6%b2%99%e7%94%b0-5/ +03fb73db218a1b92606780fc22d982ba,Indigo Kids,Miss Lo,97571105,indigo_kids.adm@hotmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e5%bf%83%e5%b0%8e%e5%b8%ab%e5%a4%aa%e5%8f%a4%e5%88%86%e6%a0%a1/ +0e37a5954643b325c70d4566df7e38ee,佛教志蓮中學,盧小姐,23218511,clnedu@chilin.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7dse%e6%87%89%e8%a9%a6%e5%a2%9e%e6%bd%a4%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +8ccc5a34e62bdf7561dccdaaed95a1a8,Superior Education Centre,Ms Yik,59160563,superior.yl2017@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%88%e7%a7%91%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-part-time-tutor/ +cad1a9ae5b37dd86932aa773f00cfc08,嘉德麗教育機構 (沙田),李太,96814517,HR@catiline.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e3%80%81%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e9%95%b7%e6%9c%9f%e5%85%bc%e8%81%b7/ +a71af1d769f25392811cac34e39537fe,Creative Power,Miss Cheng,98320892,chengcsk@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e4%b8%ad%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +54e39762cfea2445510a4fd7a41aeef2,Enlightening Education Limited,Ms Cheung,91414028,recruit@enlighteningedu.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%8d%8a%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e6%95%99-%e5%b0%87%e8%bb%8d%e6%be%b3-playgroup-teacher-teaching-assistant-tseung-kwan-o/ +575f1beb97e7dd343211eae56504dcdd,智妍教育,MS YEUNG,92812693,saraswati.ct@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-4/ +a5d2fb17c3d323b401b4340dc2717fc2,幸福文化,阮小姐,23370678,recruit@likogroup.com.hk,https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab-2/ +4b5dc6566f775ec776e3a53b76ecc5eb,康盈中英文幼稚園,盧承依校長,27723163,hongyinginfo@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-15/ +694e96de2e3be3c8b1ddb3008358a2bc,勵致研習中心,黃生,24622576,fair_ltd@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-5-6%e5%a4%a9-%e8%a7%80%e5%a1%98%e5%ae%89%e9%81%94-secondary-school-and-primary-school-tutor-5-6days-on-tat-estate-kwun-tong/ +3c2715c80a51960ee71dd3a942459cd6,早慧兒童教育中心,Ms Kong,26012080,recruit@societyofgenesis.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e6%96%87%e5%b0%8e%e5%b8%ab/ +b58fda3b86f35b3a93065d19b81c474e,東華三院馬振玉紀念中學,林嘉駿老師,24439833,lamkajin@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a0%94%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +e6245db6374d2697fde806c0de144678,香港遊戲及心理治療學苑,Kayden Chun,37554982,hr@apphk.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7sen%e8%bc%94%e5%b0%8e%e5%b0%8f%e7%b5%84%e5%b0%8e%e5%b8%ab/ +b85f673d714cd914ad3b10f00efa836e,齊齊學教育中心(佐敦),中心主任劉女士,23779039,info@cometolearn.net,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b0%8f%e5%ad%b8%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%e4%bd%90%e6%95%a6%ef%bc%9b14k-16k-month/ +f89af551db3463e5272ff7ebc60a5e5e,ACE EDUCATION CENTRE,Eric KC Lai,94083553,aceeducentre.hk@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-5/ +8ae2f76004fd9ca62fafcd3c1a3b5e3a,HONG KONG YOUNG TALENTS ASSOCIATION,MANDY CHEUNG,97025048,mandycheung0702@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e8%8b%b1%e6%96%87%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +e3374edbb0a0cb840c0993136a8dfdc7,圓玄學院妙法寺內明陳呂重德紀念中學,魏小姐,24458899,admin@clctmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e6%8b%94%e5%b0%96%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +7e36341844a9b151eeac02a6901cd13b,香港數理科技及英語教育中⼼ Hong Kong Stem & English Education Center,莊先生,62858278,admin@stem100.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-12/ +3989d7769c7fc5ddf1e326debbe23777,協恩中學附屬小學,蔡小姐,27111263,hyps@hyps.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%85%bc%e8%81%b7/ +33183e9ca06280939b5dc971e7cacd9c,御學軒集團有限公司,Enos Cheng,63604267,hr@royallearning.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e9%9d%a2%e8%a9%a6%e5%9f%b9%e8%a8%93%e5%b0%8e%e5%b8%ab/ +d5dd84bef7c2a0436e1a6050a15901e1,梁式芝書院,鄭小姐,23496626,recruitment@lscc.edu.hk,https://recruit.hkfew.org.hk/jobs/enhancement-class-tutors-for-students-after-school/ +ccf076ae8aa5a65a5445502a4b5b5864,星橋教育中心 (Starbridge Education Centre),鄧小姐,60127547,starbridgehk@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e5%b0%8e%e5%b8%ab/ +d7681d52fdd65bd55265cb9a555af5f6,保良局朱敬文中學,何小姐,26991031,master@plkcwc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e8%8b%b1%e6%96%87%e7%a7%91dse-strategy-class-%e5%b0%8e%e5%b8%ab/ +e58ff5ee147d829129c6471c7473e9f6,HKBUAS Wong Kam Fai Secondary and Primary School,Ms. Chiu,26367383,hr@hkbuas.edu.hk,https://recruit.hkfew.org.hk/jobs/part-time-tutors-math-science/ +6367d478f9c7e42b8e88fd8f308982ff,哲研教育中心,黃先生,97984698,hr@philedu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad-2/ +353cda9db540e4a82dcbad48a099f631,荃灣kidversity,Miss Lam,31690588,kidsbobo111@gmail.com,https://recruit.hkfew.org.hk/jobs/playgroup-%e5%b0%8e%e5%b8%ab-%e5%8a%a9%e7%90%86%e4%b8%ad%e5%bf%83%e4%b8%bb%e7%ae%a1/ +6bd97a95c3f859be9ad4bf447b21ac26,CHILDREN WORKSHOP 綠苗工作坊,Miss Wong,21382508,info.childrenworkshop@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a6%aa%e5%ad%90%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +605892cb4f63d11b273ccb947ba8d0c5,BLUE SKY ENGLISH EDUCATION CENTRE,Winnie,25728600,ask@treasure-global.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7-%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +446ccc0602c5f1fad4ba7016b0206b7e,明愛賽馬會黃大仙綜合服務,周姑娘,23820265,chaukm.joyce@caritassws.org.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +7f9f8c23c8b6d4d85707fb7fbf356f4a,基督教恩活堂,鄭先生,60223693,leoming422cs@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%8d%8a%e8%81%b7%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab-%e5%b0%8f%e5%ad%b8-%e6%88%96-%e4%b8%ad%e5%ad%b8/ +f683c9f3fecea8e44e23c643d2674ee5,Dunn's Education 梓峰教育,許小姐,98535867,recruit.dunn@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-%e5%b7%a5%e4%bd%9c%e5%9c%b0%e9%bb%9e%ef%bc%9a%e4%b9%9d%e9%be%8d%e7%81%a3/ +c33f8d20592d7e018e77c4408a134235,東莞工商總會劉百樂中學,朱小姐,26951336,info@lplss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e4%b8%ad%e6%96%87%e7%a7%91%e8%aa%b2%e5%be%8c%e5%b0%8e%e5%b8%ab/ +ccc99e974314ab07be5d7e1731f86685,慈航學校,曾小姐,26486834,info@chihong.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%a4%9a%e5%90%8d%ef%bc%89/ +c835ac9d6f3b2c52ccf2d4c2eec8027b,EDUKEY CENTRE FOR EDUCATION,Miss Chan,27940851,info.epc.edu@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b9%9d%e9%be%8d%e5%9f%8e/ +b02abffa636435b62aeba78961646e9c,余振強紀念中學,王名儉老師,27144161,mkwong@gm.yckmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e5%ad%b8%e7%94%9f%e5%b0%8e%e5%b8%ab%e4%b8%ad%e6%96%87/ +c7eb3872441b56be87b8761dba667b8b,明愛粉嶺陳震夏中學,梁雅燕老師,26699966,cfs@cfs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%a4%96%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab%ef%bc%88%e7%be%8e%e9%a3%9f%e8%a3%bd%e4%bd%9c%ef%bc%89/ +401dc8e3e21c89491728280e8de10007,旅港開平商會中學,張小姐,27123107,school@hpccss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e4%b8%ad%e6%96%87%e5%a2%9e%e6%bd%a4%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +32867074935dbf457eb13f53b07b94e2,山林教育Hillwood Tutorial School,Miss Amanda Ng,23779937,hillwoodtst@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-4/ +10d00799c6da88d2b8420b36b8110790,大眾教室(將軍澳廣場),蘇太,44046977,seededucation2020@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab-2/ +4f88db7fd8df1bfe716150cf464660f5,啟航教育中心,王sir / miss lam,65800045(whatsapp)/56427706(whatsapp),hksailingadventure@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e6%95%b8%e5%ad%b8%e5%b0%88%e7%a7%91%e5%b0%8e%e5%b8%ab/ +b87ec1100cab4c6860bb0b2bf7d96d5a,余振強紀念中學,楊竣皓,55340585,chyeung@gm.yckmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%a2%9e%e7%9b%8a%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%ef%bc%88%e4%b8%ad%e6%96%87%ef%bc%89-7/ +a2eb7d4382246d2e1648d0a00201e3d7,靈實恩光學校,梁小姐 (Amy Leung),27031722,mcleung@sunnyside.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8d%8a%e8%81%b7-%e6%97%a5%e8%96%aa-%e5%ae%bf%e8%88%8d%e5%b0%8e%e5%b8%ab%e5%90%88%e7%b4%84%e5%88%b6-%e7%b7%a8%e8%99%9f%ef%bc%9ass-0-5hp-daily-rated-hp/ +6860d71a7603c9104fb842d1a348a22c,拓維教育中心,Ms So,98422887,recruit@topbrighteducation.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e7%b4%85%e7%a3%a1%e8%a3%9c%e7%bf%92-2/ +b31308780570d9250b2bf5c2fb3cc88f,匯成教育中心(IBEC),Cherry Li,31089040/57424553,adminibecssp@buznetvigator.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e5%b8%ab-2/ +a2ec56017704578b555869f4fe338ca6,卓爾畫室,Raymond Chan,31149050,info@jr-art.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%92%e7%ab%a5%e7%b9%aa%e7%95%ab%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +a92862089028a2b6d092e92dacfd2dd6,香港布廠商會朱石麟中學,Cora Lam,27968323,recruit@chusheklun.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +e7f79c6c8d13c9c8baa51f82691a2d92,智趣小博士教育中心 Dr I-Kids Education Centre,Lois Chain,84912723,lois@drikids.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7-%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab-%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ +03c987ea330a35e23cfc513f5f7ac3a6,匯成教育中心(IBEC),葉小姐,26777778/54119779,adminibec@biznetvigator.com,https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89-2/ +fb351292f4062aad6060800e40a66246,香港基督教女青年會 將軍澳綜合社會服務處,周先生,27093388,ittkorecruit@ywca.org.hk,https://recruit.hkfew.org.hk/jobs/%e9%8b%bc%e7%90%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%a4%9a%e5%90%8d/ +fcbdd8576a77ce34f7495a1384448002,中華基督教會何福堂書院,江老師,24596354,hft-mail@hftc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%8e%92%e7%90%83%e6%a0%a1%e9%9a%8a%e6%9c%8d%e5%8b%99%e5%93%a1/ +faaa2cc941cbaee4e54dbb23cc7be150,賽馬會萬鈞毅智書院(政府津貼文法中學),黃小姐,24472322,eduyoung@jcmkec.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-11/ +b92b7550daf20457609cde50d14f5586,路德會沙崙堂幼稚園(慈愛分校),李麗萍校長,23260280,sckg0360to@gmail.com,https://recruit.hkfew.org.hk/jobs/%e7%9f%ad%e6%9c%9f%e5%b0%8e%e5%b8%ab%e6%94%af%e6%8f%b4%e9%9d%9e%e8%8f%af%e8%aa%9e%e5%ad%b8%e7%ab%a5/ +bfb9f0f5a1fa66d0b7ceb39674d137d7,Rainbow Creative Arts Limited,Nora Ng,52737875,hr2@rainbowarts.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%92%e7%ab%a5%e6%99%9d%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e4%b9%9d%e9%be%8d%e6%9d%b1-%e5%85%83%e6%9c%97-%e9%9d%92%e8%a1%a3-%e6%b8%af%e5%b3%b6%e6%9d%b1-%e5%b0%87%e8%bb%8d%e6%be%b3/ +d5e837520f8b641fc11f50e42d3a6a55,東華三院馬振玉紀念中學,鄭慶珠老師,24439833,chenghingchu@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e6%8b%94%e5%b0%96-%e5%a2%9e%e6%bd%a4%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e4%b8%89%e8%87%b3%e4%b8%ad%e5%85%ad%e7%b4%9a-4/ +090d2df8aa6523902ac7858c3845f67f,HKTA香港導師會,劉小姐,55961800,sandi.lau@hkta.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%a5%a7%e6%95%b8%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +e566599145c689699698a20f4b3bed0b,匯成教育中心(IBEC),Cherry Li,31089040/57424553,adminibec@biznetvigator.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e5%b0%8e%e5%b8%ab/ +9da5b00ab63f12023f71ed74b5d7beaa,中華基督教會馮梁結紀念中學,區小姐,26516033,info@flk.edu.hk,https://recruit.hkfew.org.hk/jobs/e-%e6%a8%82%e5%9c%98%e5%b0%8e%e5%b8%ab/ +ad4cb490514ec7e6047f331ae070c9f6,循道衛理楊震社會服務處 家庭健康教育及輔導中心,黃先生,21714111,leowong@yang.org.hk,https://recruit.hkfew.org.hk/jobs/%e7%be%a9%e5%b7%a5%e8%a8%88%e5%8a%83%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab-2/ +8ed56041a8c66a44254ca2b416244999,智趣小博士教育中心 Dr I-Kids Education Centre,Mr. Wong,52324018,application@idrkids.com,https://recruit.hkfew.org.hk/jobs/%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%a8%e8%81%b7%e5%b0%8e%e5%b8%ab-full-time-tutor-%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor/ +f6b2fa5c376c02c3b5091e544bba6f21,St. Paul's Secondary School,Ms. Li,25773836,spssmail@spss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%9d%9e%e8%8f%af%e8%aa%9e%e4%b8%ad%e6%96%87%e6%95%99%e5%ad%b8%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7/ +fea4a0303877ba03ad4ea74f17ce5a23,心計劃上門訓練,Josie Yung,53302658,josie@sencares.hk,https://recruit.hkfew.org.hk/jobs/%e3%80%8c%e5%bf%83-%e8%a8%88%e5%8a%83%e3%80%8d%e4%b8%8a%e9%96%80%e7%89%b9%e6%ae%8a%e6%95%99%e8%82%b2%e8%80%81%e5%b8%ab-2/ +01f0867bd2d796cd50a3ee054c5c4739,One Plus One Educational Centre,蘇小姐,22468139,yuenlong@1plus1edu.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-10/ +1a2ea913455d82163071a3a6c7609438,BLUE SKY ENGLISH EDUCATION CENTRE,Winnie,TEL:25728600WHATSAPP:90463429,marta.dl@outlook.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7-%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +96725f4cae2abf07e75a40e49339ec0a,文化教育 (補習社),鄭生,95878700,gregenius71@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e7%8f%ad%e8%80%81%e5%b8%ab-%ef%bc%8f%e5%85%a8%e8%81%b7%e5%ba%97%e9%95%b7/ +44828f1363177ffdc56cfebaf593d7c4,KA CHUNG EDUCATION CENTRE,黃小姐,55923953,kk21717@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%93%a1/ +363fd499d77f1c3838f745bb9e29fd27,Tiffany Lau Vocal Performance Academy,Miss Lok,35472550,enquiry@tlvpa.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%88%e7%b4%84%e9%9f%b3%e6%a8%82%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +ec756419bc526e673680910868b081bf,神召會康樂中學,湯小姐,26520698,application@gc.hebron.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e6%95%b8%e5%ad%b8%e8%a3%9c%e7%bf%92%e7%8f%ad/ +55119adc66d0c42427bd8f47190ff1c8,智恒教育中心有限公司,Raymond Wong,21267633,raymond.wong@i-software.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e9%9b%bb%e8%85%a6%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7steam%e5%b0%8e%e5%b8%ab-i-t-trainer-steam-trainer-2/ +19b4037c5399545c72d4473b432a289b,東華三院蕭旺李滿福幼兒園,李小姐,25802273,swlmfns@tungwah.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-19/ +a38ed0758dbd95ca7ec3e4c55c398502,連青網絡-香港神託會青少年綜合服務中心,陳小姐,90123610,kayan.chan@stewards.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e5%b0%8e%e5%b8%ab/ +64fbe58efa245e89452827f34115712c,循道衛理楊震社會服務處 家庭健康教育及輔導中心,黃先生,21714111,fhe@yang.org.hk,https://recruit.hkfew.org.hk/jobs/%e7%be%a9%e5%b7%a5%e8%a8%88%e5%8a%83%e6%b4%bb%e5%8b%95%e5%b0%8e%e5%b8%ab/ +9713377c4e5300934b6d2cb13e467ba0,御學軒集團有限公司,Enos Cheng,63604267,hr@rlg.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +fa41383208cf179c92804e592327067e,臻越教育中心,Chloe Chung,46839183,zhenyue2021@gmail.com,https://recruit.hkfew.org.hk/jobs/part-time-%e5%8c%96%e5%ad%b8%e5%b0%8e%e5%b8%ab/ +c4d09031c0a6485946d0288f170735f1,Learn together Limited,Gladys Yeung,54362935,apply.learntogether@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%b0%88%e7%a7%91%e7%8f%ad%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-%e5%9c%9f%e7%93%9c%e7%81%a3/ +96d0be6f9c0928e8390767f62a6b35ea,浸信會沙田教育中心,陳小姐,82003351,baptistec@hkstbc.org,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%85%bc%e8%81%b7-%e6%b2%99%e7%94%b0%e5%8d%80/ +91f455718ef449f203016adebcc3b9fc,Childwalker童行者,Ms Catherine Ho,95585099,catherineho33@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab%e6%b8%af%e5%b3%b6%e5%8d%97%e5%8d%80/ +8b524fd43b3a04746a5ff83aebc41e76,中華基督教會燕京書院,伍小姐,23879988,recruit@yenching.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%81%af%e8%aa%b2%e6%b4%bb%e5%8b%95%e5%8a%a9%e7%90%86%e5%b0%8e%e5%b8%ab/ +6c9abcfd2fae6b535f6774d50e075da8,Man Kai Education,Miss Chan,26688294,eunice@monkeymaster.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%83%e6%9c%97%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-part-time-tutor-5/ +c37f2e7441372c81401955b744208311,港九潮州公會中學,Emily Ngan,23964187,recruit@ccpass.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%ad%b8%e7%bf%92%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab/ +4d79c27d6294ff4c6713f4877ea41ec7,Delia (Man Kiu) English Primary School,Ms Leung,24325123,ty@deliagroup.edu.hk,https://recruit.hkfew.org.hk/jobs/after-school-chinese-tutorial-class-tutor-2/ +8c484acfd33be7f5759ab6b1c8e10995,Ginny Education Center,Kevin Wong,95243357,ginnyeducation@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +164c9b88fbe4e67e7fd501c07fca2275,千瑪教育中心,連佩文,51341789,admin@marchcentre.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +0822bd50c389f36f464ffcb2ea62de75,學孜教育有限公司,Henry Chu,60124257,hotedu.info@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e5%b0%8f%e7%b5%84%e5%b0%8e%e5%b8%ab/ +8b18b8021be53212f95752d9b518895e,保良局陸慶濤小學,藍小姐,27010011,cmlam@plklht.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%88%e5%94%b1%e5%9c%98%e6%8c%87%e6%8f%ae%e4%bb%a3%e8%aa%b2-29-11-28-2%e6%9c%9f%e9%96%93/ +85bf2458d8c258568c3f86ca6856b089,聖公會聖西門呂明才中學,李小姐,24598236/39567900,recruit@skhsslmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%96%87%e6%86%91%e8%a9%a6%e8%8b%b1%e8%aa%9e%e7%89%b9%e8%a8%93%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +85908dce38939fea84cf672c90bace37,進研學習中心,楊小姐,67909241,infocontemporaryedu@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%a0%85%e5%b0%bc%e5%9c%b0%e5%9f%8e%ef%bc%89/ +1a1523e792b771811c10ce0fddd29794,廠商會蔡章閣中學,Anson Chan,24520681,ac@cmacck.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%91%a8%e4%b8%89-%e5%91%a8%e5%9b%9b%e8%8b%b1%e6%96%87%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ +a2a35a797635782e476201203f80fdeb,星級第一教育中心,Ms Hui,64851211,1stedu.hr@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8f%e7%b5%84%e5%8a%9f%e8%aa%b2%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +17d33ca218e1cc0d0e7aaca5037b1b8f,香港真光書院,莫潔儀老師,28711214,hktlcoff@hkstar.com,https://recruit.hkfew.org.hk/jobs/%e8%88%9e%e5%8f%b0%e9%9f%b3%e9%9f%bf%e5%b0%8e%e5%b8%ab/ +ef85f850aaab3eb7fb73f0c201c27fd5,現代小學士有限公司,MS Poon,97290919,bachelor.junniepoon@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-part-time-tutor-%e9%95%b7%e6%9c%9f-%e7%9f%ad%e6%9c%9f%e4%b9%9f%e5%8f%af/ +8964eff2ec44aff6f49cf04d553bec8a,進才教育中心(土瓜灣),施小姐,61753811,gslc.edu@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b0%8f%e5%ad%b8-%e4%b8%ad%e5%ad%b8%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ +8c4e3519d3793e51d214787ca725167a,文耀教育中心,蔣老師,93290888(可Whatsapp聯絡),myeducen@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e5%b0%8e%e5%b8%ab/ +53294f96c3932cdf975f2c95faa4e818,啟航教育中心,王sir / miss lam,65800045(whatsapp),hksailingadventure@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e5%b0%88%e7%a7%91%e5%b0%8e%e5%b8%ab/ +3fc29530c725233de33ac02e35d6ed18,愛家庭賽馬會成長中心,黃姑娘,82011163,ltlec@skhwc.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-13/ +bc53208979d081a2ac4008ac62d1390b,Good Hope School 德望學校,Ms. Sonia Wong,23210250(Ext.22),recruitment@ghs.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%9c%8b%e8%a1%93%e5%ad%b8%e6%9c%83%e5%b0%8e%e5%b8%ab/ +5b97182eb8c122a28e60620f2fbda474,Dr I-Kids 智趣小博士教育中心,Lois Chan,84912723,application@idrkids.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%85%a8%e7%a7%91%e8%bc%94%e5%b0%8e%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e4%bd%95%e6%96%87%e7%94%b0-%e7%ad%b2%e7%ae%95%e7%81%a3-%e5%b0%8f%e8%a5%bf%e7%81%a3/ +4e88acd9f46e791b78dcccc595dba502,伊利沙伯中學舊生會湯國華中學,校長,26175000,info@qts.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%96%87%e6%86%91%e8%a9%a6%e7%a7%91%e7%9b%ae%e8%aa%b2%e5%be%8c%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab/ +4cfa862e76b81dc2fd76ad23f6ed1954,東華三院郭一葦中學,莊小姐,24471258,office@twghkywc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e9%ab%98%e4%b8%ad%e8%aa%b2%e5%be%8c%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ +c316f7fe15ac9b24f75696905bcd0bc1,豆豆導師招聘網,陳小姐,67480761,careers@education-plus.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%88%b0%e6%a0%a1%e5%b0%8e%e5%b8%ab__%e4%b8%ad%e4%b8%80%e4%b8%ad%e4%ba%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab__%e6%af%8f%e7%af%80120/ +1129941093f97af04de1680cfcc85e8f,佛教何南金中學,莊小姐,23400871,recruit@bhnkc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab/ +985a5c2ac6edf21a31d8247594ff529a,Happy Workshop樂斯音樂藝術坊,Ann Leung,95406014,happyworkshop09@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +5f883789705a5852afe658313101e391,基督教香港信義會屯門青少年綜合服務中心,趙姑娘(社工),24621700,yuenshanchiu@elchk.org.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-9/ +d2d17bc72431692d2c0269488a3a04ed,梓峰教育(石門),林小姐,36111948,Jessica.lam@dunn.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-2/ +1a72893a6973f5cf6953436ed3cf3085,香港道教聯合會圓玄幼稚園,招校長,24984636,hktayykg@yuenyuenkg.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8d%8a%e6%97%a5%e9%9d%9e%e8%8f%af%e8%aa%9e%e5%ad%b8%e7%ab%a5%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab-half-day-teacher-supporting-non-chinese-speaking-students-ncs-2/ +5ffcf81e59269be9134554fc53bd411e,基督教宣道會華基堂青年中心,黃小姐,25516629,candy@cmawkc.org,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e8%aa%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e7%89%b9%e6%ae%8a%e5%ad%b8%e6%a0%a1-2/ +81b56988b3923c9116423454acb62204,香港紅十字會雅麗珊郡主學校,劉小姐,23401022,pars@redcross.org.hk,https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%a7%91%e8%aa%b2%e5%be%8c%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab/ +88fae0fc1e84c8ab964bf54214ebf060,香港青少年服務處 賽馬會麗城綜合青少年服務中心,MISS CHAN,24148283,bgit@hkcys.org.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-8/ +c460ee30a324181c78b82c406328cd5e,兼職導師招聘網,陳小姐,98658294,hkeducationcenter01@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e6%9f%b4%e7%81%a3-%e6%af%8f%e5%b0%8f%e6%99%8280/ +e85947c8f6e4503937b7f02ae90b7b1a,佛教林炳炎紀念學校,吳劍峰先生,24220125,info@blbyms.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%88%e7%b4%84%e6%ad%b7%e5%a5%87%e6%95%99%e7%b7%b4/ +c5ba4c7d58420bf8f8fea32db626a13e,展才能通識教育集團,陳小姐,35830137,tutor.gegroup@gmail.com,https://recruit.hkfew.org.hk/jobs/%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab%e6%95%b8%e5%ad%b8%e9%a1%9e-%e6%b2%99%e7%94%b0%e5%8d%80-%e8%a7%80%e5%a1%98%e5%8d%80/ +df600057d041eb02545af8fe8504b96e,明愛賽馬會社區書院-荃灣,陳小姐,36103715,cb.twpa@cice.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%ad%b8%e8%8b%b1%e6%96%87%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9c%8d/ +7000c24e8d48b1b140851ed7c7b09a2c,彩虹邨天主教英文中學,林小姐,23203594,enquiry@choihung.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%be%af%e8%ab%96%e9%9a%8a%e6%95%99%e7%b7%b4/ +656a5b336cb51dcbe1c62081ab0c8ec4,香港數學奧林匹克學校/香港數學學校,梁小姐,25771148,general@hkmos.org,https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e5%b0%8e%e5%b8%ab-3/ +73970c7dfe834e844b6f33df6889c02b,金巴崙長老會耀道中學,莊小姐,24730777,info@cpcydss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab%e8%8a%b1%e8%97%9d%e5%8f%8a%e9%a3%be%e5%93%81%e8%a3%bd%e4%bd%9c%e3%80%81%e9%ab%ae%e5%9e%8b%e8%a8%ad%e8%a8%88%e3%80%81%e6%94%9d%e5%bd%b1%e5%8f%8a%e5%89%aa/ +ba5be385045e9b55f45caeaa4f999d3c,LITTLE BEANS CLASSROOM,Ms Lau,93828701,littlebeansclassroom@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%89%b5%e6%84%8f%e7%95%ab%e5%ae%a4%e5%8a%a9%e7%90%86-%e5%b0%8e%e5%b8%ab/ +b612fa45605314da722387b5b22cb1ea,Children initiation Association Education Centre,湯先生,64435475,Hkcia.edu@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%88%e7%a7%91-%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +d15d7df35cc79443f5bd0512d7bd5841,裘錦秋中學(屯門),校務處李小姐,24611555,mail@jcctm.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%84%e7%a7%91%e6%8b%94%e5%b0%96%e5%8f%8a%e8%a3%9c%e5%ba%95%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +a9b59b3f077ab40aac1617301babe91e,順德聯誼總會翁祐中學,姚小姐,31570632,info@stfa-yyc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8c%96%e5%ad%b8%e7%a7%91%e5%b0%8e%e5%b8%ab-3/ +69ef30afba489079db2cb2d65b310d8a,Little Master Education Centre,Eve Ching,55331762,littlemasterhongkong@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e6%88%96%e5%85%bc%e8%81%b7%e8%a3%9c%e7%bf%92%e7%8f%ad%e5%8a%a9%e7%90%86-%e5%b0%8e%e5%b8%ab-%e5%9c%9f%e7%93%9c%e7%81%a3%e5%8d%80/ +0f90fd0c70831f02d9b5566cefb2f4b7,沙田浸信會,陳小姐,26325000,baptistec@hkstbc.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e6%b8%af%e5%b3%b6%e3%80%81%e4%b9%9d%e9%be%8d%e5%8d%80%e3%80%81%e5%a4%a7%e5%9f%94%e3%80%81%e5%85%83%e6%9c%97%e5%8f%8a%e6%b2%99%e7%94%b0%e5%8d%80/ +41e590d135ecba1d6525f003fd539cf1,香港四邑商工總會陳南昌紀念中學,林小姐,27410326,info@cnc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e9%ab%98%e4%b8%ad%e5%af%ab%e4%bd%9c%e5%b0%8e%e5%b8%ab-2/ +bbe960de5e8d1f564ebfc99c8d978466,iMusic Arts Gallery,Wong,96580812,cs@imusicarts.com.hk,https://recruit.hkfew.org.hk/jobs/%e7%b9%aa%e7%95%ab%e8%a6%96%e8%97%9d%e5%b0%8e%e5%b8%ab-2/ +af3ba3c1ca1df2ff74f642384af6a419,匯萃小學士教育中心,Miss Chan,55117628,schoolrecruit201702@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab-2/ +8f6e95188093423d915b82994bbb83fe,EVI Services Limited,Mr. Choi,35890840,career@evi.com.hk,https://recruit.hkfew.org.hk/jobs/part-time-stem-course-tutor/ +0462529c2a5db80fee4f666a853cd6b2,Easey Add Education Centre,Ms Chong,27940850,maggiechong97@gmail.com,https://recruit.hkfew.org.hk/jobs/%e8%81%98biology-economics-bafs-%e5%b0%8e%e5%b8%ab/ +2b36fa8d30440b794e023f7f060bca79,心愛人事顧問,yan,63834168,sales@iproactive.com.hk,https://recruit.hkfew.org.hk/jobs/art-tutor/ +82aa2b79527a756a90d3b0fd2129d3d3,佛教沈香林紀念中學,林麗麗,24377004,info@bsc.edu.hk,https://recruit.hkfew.org.hk/jobs/dse%e6%8b%94%e5%b0%96%e8%a3%9c%e7%bf%92%e7%8f%ad%e6%95%99%e5%b8%ab%ef%bc%88%e6%95%b8%e5%ad%b8%e7%a7%91-%e5%9c%b0%e7%90%86%e7%a7%91-%e7%94%9f%e7%89%a9%e7%a7%91%ef%bc%89/ +788bb7bc0d46ea14bf8ccb8b38677fa0,Greenway Education Centre,馬主任,55230378,greenwaytlb@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e6%96%87%e5%93%a1%e5%8f%8a%e5%b0%8e%e5%b8%ab/ +302b6cf1bf66ef698325d268779ba448,新聰穎教育中心 Talent Development Training Centre Limited,麥主任 Mrs. Mak,93580856,fionamak_1000@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab-2/ +775e8e3a58a025c341942312c3f88904,啟航教育中心,Miss Lam,56427706(whatsapp),hksailingadventure@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%88%b0%e6%a0%a1%e6%97%a5%e8%aa%9e%e5%b0%8e%e5%b8%ab/ +16340241401411a36eaa88da21841047,One Plus One Educational Centre,Miss Lau,22468139,wts@1plus1edu.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e4%b8%ad%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab-%e5%bd%a9%e8%99%b9-2/ +f6b9a59c7f8f2571d76c4540e4fa9388,大衆教室(薈學坊) 有限公司,吳先生,57230080,plcsmarta3c@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e6%96%87%e3%80%81%e8%8b%b1%e6%96%87%e3%80%81%e6%95%b8%e5%ad%b8/ +b9e85a71da493723a51e44a431f9eaac,東華三院馬振玉紀念中學,陳銘勤,24439833,chanmingkan@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%b1%83%e7%90%83%e9%9a%8a%e6%95%99%e7%b7%b4-2/ +6edd2b05bd4473a07f064d13390c11ab,救世軍,人力資源部,27832366,hrd.recruit@hkm.salvationarmy.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-%e5%a4%a7%e5%9d%91%e6%9d%b1%e9%9a%8a%e6%95%99%e6%9c%83-%e5%b7%a5%e4%bd%9c%e5%8f%83%e8%80%83%e7%b7%a8%e8%99%9f-ptt-thtc-05-22/ +03222135e5babfe9dd70902f69333ea1,鄰舍輔導會 賽馬會天水圍綜合服務中心,嚴姑娘,26178816,tssc@naac.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-9/ +54eee4216edbeea09086d4b742b1b044,皇仁舊生會中學,黃小姐,24975688,info@qcobass.edu.hk,https://recruit.hkfew.org.hk/jobs/english-classes-tutors/ +4542a8f1d5ef8b6e6370804ac2224745,香港中華基督教青年會顯徑會所,莊季陶,92191989,ktchong@ymca.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab-4/ +eba3222dcb794a25112287b0a9817b3b,THE BLUE GALLERY (CWB)LIMITED,PAULA LO,96277997,info@xiaoyanschoolofart.com.hk,https://recruit.hkfew.org.hk/jobs/%e3%80%90%e7%95%ab%e7%8f%ad%e5%b0%8e%e5%b8%ab%e3%80%91full-time/ +18db57ea532385a3c31bee354f8f93cd,培星園教育中心,Sylvia Lo,60798428,stargardeneducation@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%95%99%e5%b8%ab-%e5%b0%8e%e5%b8%ab/ +e60e104f90e01c21d72fa95852457b5a,新家園協會賽馬會港島東服務中心,黎姑娘,28072188,hr.hkisc@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e6%8f%90%e5%8d%87%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%88%9d%e4%b8%ad/ +5a41df96d82a352182b3da853bfff081,Seasons Music Studio,Mr. So,93003391,seasonsmusicstudio@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%8f%8a%e5%85%bc%e8%81%b7%e9%9f%b3%e6%a8%82%e5%b0%8e%e5%b8%ab/ +856cb7c8cac5a4506c4fa9d459d9f5c1,My Gym Children's Fitness Center(TST),Ms Lam,23348334,mygymhk@outlook.com,https://recruit.hkfew.org.hk/jobs/playgroup-lead-teacher-full-time/ +387aaed2c8afbfe89bcea9be063e43c8,Bright English Education Centre (Shek Mun),Frankie Cheung,92316114,bright.edu.hr@gmail.com,https://recruit.hkfew.org.hk/jobs/full-time-english-phonics-teacher-shatin-shau-kei-wan-5-mins-walk-to-mtr-station/ +fbe1c75f20a5fa8786c3aa54fd985710,大衆教室(佐敦恒豐中心分校),李小姐,91462842,sinotrend13@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%b0%8e%e5%b8%ab/ +60eb06db550897ef067f1f0acc072cac,SMALL KIDS BIG ART CENTER,MISS CHUNG,67084616,smallkidsbigarthk@gmail.com,https://recruit.hkfew.org.hk/jobs/part-time-art-teacher-part-time-english-teacher/ +dc671ec36871e7bd43f580078933b3ea,聖雅各福群會 延續教育中心,Daphne Suen,28313268,daphne.suen@sjs.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8-%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87-%e6%95%b8%e5%ad%b8-%e5%b0%88%e7%a7%91%e7%8f%ad%e5%b0%8e%e5%b8%ab%e7%b6%b2%e8%aa%b2%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7-%e6%95%99/ +962854f5db9735a160fb6052124804e0,小豆苗教育中心,柳小姐,26489059,info@seedling-edu.com,https://recruit.hkfew.org.hk/jobs/s4574-%e5%b0%87%e8%bb%8d%e6%be%b3%e5%8d%80%e5%88%b0%e6%a0%a1%e4%b8%ad%e5%ad%b8%e5%a5%a7%e6%95%b8%e8%aa%b2%e7%a8%8b/ +c7e85ab3bac0eef812f3fb5f31f8d153,/,Mr. Chan / Ms. Wong,97914544,popularlearningskw@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%95%99%e5%ae%a4%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e6%96%87-centre-tutor-chinese-%e5%85%bc%e8%81%b7-part-time-%e5%85%a8%e8%81%b7-full-time-2/ +9e83918f433532154fcc56e2cfa94e53,卓峰優越教育中心,Mr Lam,93173553,recruitment@apex.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab-phy-chem-bio-math/ +9672089796bdbbcf8a40a8f98e37af8a,Loving Kids Community Service Co. Limited,周姑娘,21173675,iona.chow@lovingkids.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab-2/ +e346504fef33fcb8dd1a6807aaca1904,鳳溪第一中學,馬小姐,26700366,fk1ss@fk1ss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%88%9d%e4%b8%ad%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e4%b8%bb%e7%a7%91/ +0a4e1afe892fdc86126fe96cd4952a13,聖公會林裘謀中學,郭小姐,26488222,skhlkmss@skhlkmss.edu.hk,https://recruit.hkfew.org.hk/jobs/coaches-for-english-public-speaking-solo-verse-speaking/ +e1c0fbec58d9917cc79b5ec9781748cd,香港布廠商會朱石麟中學,林小姐,27968323,lamwf@chusheklun.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e7%8f%ad%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab/ +cad62a61f3ebcbd1891b0f46487e41a9,Bishop Hall Jubilee School,Ms. Wong,23363034,hr@bhjs.edu.hk,https://recruit.hkfew.org.hk/jobs/tutors-for-enhancement-classes-after-school-classes-saturday-remedial-classes/ +f2828f36f2d1c927f12290d90cc33aad,天主教聖華學校,何敏淇老師,26924593,hmk@littleflowerschool.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%90%88%e5%94%b1%e5%9c%98%e5%b0%8e%e5%b8%ab/ +67ff0c0e94a71576b0e36cf70db27d65,Tak Sun Secondary School,Mr Mervyn Lam,23174339,hrd@tsss.edu.hk,https://recruit.hkfew.org.hk/jobs/coaches-and-course-tutors-service-agreement-based-4/ +ea4cd23f27b8b85c6834d2412c1617a5,石籬天主教中學,黃小姐,24291221,email@slc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e6%94%af%e6%8f%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +26cdf8524b7da56baaa3a997960523dd,大衆教室(田心村),Ms Li,67759354,populartaiwai@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab%ef%bc%88%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%ef%bc%89/ +cb3a089e2749147ba3d5238b3feee32d,藍田街坊福利會,盧承依女士,27723163,hongyingkinder@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-12/ +485419704600e9be2474e8e066169523,大衆教室(田心村),Fanny Li,67759354,yffanny@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e5%8f%8a%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ +2bf9958366c1f035db11196f67dae5ad,小月亮創作室,邱小姐,68781132,littlemoonart@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e7%95%ab%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +e8cb6f23c95d33af50424ef0f1b45822,薈晉教育中心,張小姐,31532697,eduirradiate@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%95%99%e8%82%b2%e4%b8%ad%e5%bf%83%e8%a1%8c%e6%94%bf%e4%b8%bb%e4%bb%bb%ef%bc%88%e5%85%bc%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab%ef%bc%89/ +239e3f8624738c9847de480648795377,豆豆導師招聘網,吳小姐,67480761,hihimoon@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%a4%a7%e9%87%8f%e6%8b%9b%e8%81%98%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab/ +5833029306ca819352a0113a94c27b80,Eye Level Little Scholar Education Centre,Centre Manager,21857205,enquiry-enopi@hotmail.com.hk,https://recruit.hkfew.org.hk/jobs/full-time-mathematics-senior-mathematics-teacher/ +26b0adc9bd370fcd3d653350efbf4c7b,天主教新民書院,羅小姐,23857812,ncc@newman.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%8e%92%e7%90%83%e9%9a%8a%e6%95%99%e7%b7%b4-2/ +7ec9e7ee06629c8463974b984cc96607,小童星訓練學院,林小姐,93649364,smallmo@momocastingtalentschool.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%80%81%e5%b8%ab%ef%bc%8f%e5%b0%8e%e5%b8%ab%ef%bc%88%e6%8f%90%e5%8d%87%e5%ad%a9%e5%ad%90%e8%87%aa%e4%bf%a1%e5%bf%83%e7%9a%84%e8%aa%b2%e7%a8%8b%ef%bc%89-2/ +84ca8a51b9b4d97fdf4c560c3ca3a82a,Topswood Education Centre,Vincent Chan,54076614,Topschan16@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-%e9%9d%92%e8%a1%a3-%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7/ +6d7b0835782480cd8fffd43a6b9e39f2,大眾教室(將軍澳廣場),蘇太,94044044,seededucation2020@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab/ +36e1ebfeef577b5b3f08d36990968f25,棉紡會中學,區小姐,24220028,csa@csa.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +3fa22a56b4d7a4f7b375c57a854d8ad9,香港基督少年軍,Sarah Chan,24456722,stedfast_house@bbhk.org.hk,https://recruit.hkfew.org.hk/jobs/%e7%8f%ad%e7%b5%84%e5%b0%8e%e5%b8%ab-%e5%8d%80%e6%9c%ac%e8%a8%88%e5%8a%83/ +cea48f8fa50e9aef75caa36dee7b0180,嶺南中學,袁小姐,28916966,recruit@lingnan.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%84%e7%a7%91%e8%aa%b2%e5%be%8c%e6%94%af%e6%8f%b4%e5%b0%8e%e5%b8%ab-%e5%85%bc%e8%81%b7/ +4f151695ead81663e33cb5563a1279d8,樂善堂顧超文中學,陳小姐,24295171,email@lstkcmss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%88%e7%b4%84%e8%be%af%e8%ab%96%e9%9a%8a%e5%b0%8e%e5%b8%ab/ +e498c87f5ed6ec914effe6a0c7044c7a,Wah Yan College Hong Kong,Ms Mok,25722251,human_resources@wahyan.edu.hk,https://recruit.hkfew.org.hk/jobs/part-time-tutors-various-subjects/ +5d70141a463d2378d3f48cabcdbb57cd,晉斯琴行 AMA Music Centre,朱小姐,55050322,amamusiccentre@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab-2/ +b37b35515bb71948df91187b99ed0804,保良局錦泰小學,潘小姐,23262886,plkgpsrecruit@gmail.com,https://recruit.hkfew.org.hk/jobs/%e6%95%99%e5%ad%b8%e5%8a%a9%e7%90%86%e5%85%bc%e8%aa%b2%e5%be%8c%e5%8a%9f%e8%bc%94%e7%8f%ad%e5%b0%8e%e5%b8%ab%e5%90%88%e7%b4%84-3/ +efd7e570bf18a4372141f8dae0ecbf88,明愛樂恩學校,柳小姐,23100440,info@cmts.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e8%88%9e%e5%b0%8e%e5%b8%ab-2/ +544565ce720812bbf2603187f57915ac,研樂補習學校(葵星中心),Mr Martin Tong,93840216,martintong0914@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +b78828aa08370b170627540febca716c,青年專業書院 YMCA College of Careers,Maria Wong,27833513,sfwong@ymca.org.hk,https://recruit.hkfew.org.hk/jobs/adobe-illustrator-photoshop-and-indesign%e6%97%a5%e9%96%93-%e5%a4%9c%e9%96%93%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-pt-tutor_da/ +b147f3ff959ea6eecbec6f9b53e35d55,基督教宣道會華基堂青年中心,張姑娘,25380644,cheunghy@cmawkc.org,https://recruit.hkfew.org.hk/jobs/%e8%8a%b1%e5%bc%8f%e8%b7%b3%e7%b9%a9%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +878ede16544e419178ca31b1183bf2a8,香港教育工作者聯會黃楚標中學,陳小姐,21094005,contact@wcbss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b9%b3%e6%9d%bf%e9%9b%bb%e8%85%a6%e9%9b%bb%e7%b9%aa%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +c4861bebdee597b7a6febdcf5f29ccd1,救恩書院,黃小姐,26608308,recruit@kyc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%a0%a1%e5%a4%96%e6%95%99%e8%82%b2%e6%9c%8d%e5%8b%99%e5%b0%8e%e5%b8%ab/ +26fa13b0c6bf56c66375062fa0b0b8fb,循道衛理楊震社會服務處 何文田青少年綜合發展中心,區本陳姑娘,27181330,heidychan@yang.org.hk,https://recruit.hkfew.org.hk/jobs/%e8%a8%98%e6%86%b6%e5%8a%9b%e5%b0%8e%e5%b8%ab%ef%bc%88%e8%81%b7%e4%bd%8d%e7%b7%a8%e8%99%9f%ef%bc%9ahic-cbp-m%ef%bc%89/ +860d23df7e5aea44d4459354f10fd9e3,聖雅各福群會 樂寧兒童發展中心,劉姑娘,25962500,heidi_lau@sjs.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%90%84%e9%a1%9esen%e5%85%a5%e6%a0%a1%e5%b0%8e%e5%b8%ab-%e7%b6%b2%e4%b8%8a%e8%aa%b2%e5%b0%8e%e5%b8%ab/ +8640d37ef7c69d8095026393e338a858,嘉言教育機構,Venus Lo,35654432,Arts@greenville.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e8%88%88%e8%b6%a3%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +6af0a5651c86ce0b0be01769ac9c0886,思諾教室(大埔),林小姐,34856905,silokrecruit@gmail.com,https://recruit.hkfew.org.hk/jobs/part-time-tutor-2/ +99d3865e85cecd924c38fb3499a5c5f2,基督教女青年會丘佐榮中學,行政主任李小姐,27117159,info@htyc.edu.hk,https://recruit.hkfew.org.hk/jobs/activity-coaches-and-tutors/ +b9fc46c5eb83e739d8cd3591f78de4a5,香港宣教會粉嶺家庭中心,陳先生,26764010,gordon_chan@hkecffc.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e8%b7%86%e6%8b%b3%e9%81%93%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +8889ff38955c6aaccbba9cf4dfc128ff,Oxfam Hong Kong,Ida Wong,25202525,hr@oxfam.org.hk,https://recruit.hkfew.org.hk/jobs/course-instructor-one-year-consultancy-contract-2/ +0412f27217985d340d36a6c56dae9fd9,佛教孔仙洲紀念中學,溫老師,23226915,mail@bhscmc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e7%84%a1%e4%bc%b4%e5%a5%8f%e5%b0%8e%e5%b8%ab/ +0418b566215f96b3d18ce605e17ab0dc,Sacred Heart Canossian College,Ivy Ng,25506111,office@ecampus.shcc.edu.hk,https://recruit.hkfew.org.hk/jobs/english-language-tutorials-teacher/ +8012b277ce9da87309dd24d3ebbc814a,Steamlabo Education Centre,China Chung,98210908,hr@steamlabo.hk,https://recruit.hkfew.org.hk/jobs/tseung-kwan-o-full-time-teacher-kindergarten-to-junior-school/ +0f6c5768885d2d98f1a10d100a831bd5,Pharos Tutorial Centre,Ms Ng,96300998,donnanwl.dn@gmail.com,https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-4/ +040ff6817ae85dc79854a32eb587ba80,保良局方王錦全小學,麥小姐,24661882,general@eclass.plkfwkc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%9c%8b%e7%95%ab%e5%b0%8e%e5%b8%ab-2/ +cde64acdc1aa2644455aa042405a30b7,路德會新翠少年中心,傅生,26912102,chorfung.fu@hklss.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e9%a4%98%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-7/ +496861be711f982641d4ecdd65ba85b0,Thrive 1+國際兒童邏輯思維遊戲互動學習中心,顧老師,69902616,nine@thrivehaba.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e4%b8%ad%e5%bf%83%e5%8a%a9%e7%90%86/ +8a20ebe64b5e0179d08434062bd246c9,香港道教聯合會青松中學,余小姐,27274315,ccn-c30@ccss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%8f%90%e4%be%9b%e3%80%8c%e4%b8%ad%e4%b8%80%e7%b4%9a%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab%e3%80%8d%e6%9c%8d%e5%8b%99/ +153c72f628da3da41d4bb2ad9055177c,一沂博學教室,Ms Leung,61178526,yieducation21@yahoo.com,https://recruit.hkfew.org.hk/jobs/%e8%aa%a0%e5%be%b5%e5%85%bc%e8%81%b7%e5%8a%a9%e6%95%99%e5%b0%8e%e5%b8%ab-4/ +af62f599409b24d75fa8f4c3f32ca375,/,陳小姐,26328953,baptistec@hkstbc.org,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-8/ +e975c2945a9be68bf61d2959ad8f2478,Stem & English,莊先生,65648596,admin@stem100.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%8d%8a%e8%81%b7-%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-2/ +2fb6f6188306f063de767c0300a85622,聖雅各福群會 延續教育中心,Gin Keung,28313268,gin.keung@sjs.org.hk,https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-7/ +c4daf65dd7e7dd61a1cb1d8cccd5fb5f,東華三院馬振玉紀念中學,呂君豪老師,24439833,luikwanho@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%94%80%e6%a8%b9%e5%8f%8a%e6%ad%b7%e5%a5%87%e5%b0%8e%e5%b8%ab/ +800e29f0f7d0cfbc08c3005dad2a6fef,慈幼英文學校,葉小姐,28843581,admin@ssshk.edu.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e7%a7%91%e5%b0%8e%e5%b8%ab-%e5%85%a8%e5%b9%b4%e7%89%b9%e5%ae%9a%e6%97%a5%e5%ad%90-2/ +fb0cdaba6c78336ba8893174ab2b4de2,佛教黃焯菴小學,劉小姐,25762638,cua_mail@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e8%aa%b2%e5%be%8c%e8%bc%94%e5%b0%8e%e8%80%81%e5%b8%ab-%e5%85%bc%e8%81%b7/ +475e86a16abd1ebe5edbfe11514c6929,藍田街坊褔利會,盧先生,27753101,dennislsc@gmail.com,https://recruit.hkfew.org.hk/jobs/%e8%a8%97%e7%ae%a1%e5%b0%8e%e5%b8%ab-2/ +7c9096ccdbd4eefb89292ba566962950,佛教茂峰法師紀念中學,Ms Anne HUNG,24457171,bmf-mail@bmf.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%99%ae%e9%80%9a%e8%a9%b1%e6%9c%97%e8%aa%a6%e5%b0%8e%e5%b8%ab/ +8ee80944c1d64129e2948f30c8a48b09,東華三院馬振玉紀念中學,謝爾溱老師,24439833,tseyitsun@cyma.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8f%a4%e7%ae%8f%e7%8f%ad%e5%b0%8e%e5%b8%ab1%e5%90%8d/ +14c9a61869fbdec23d3468c5c73ac654,同學坊教育中心(旺角),MS Winni Tong,27826922,mongkok@edfun.com.hk,https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +cb21d7dbce3b53837237c99e2dfa76c1,HeSheLearn 網上教學平臺,何先生,94450283,victor@heshelearn.com,https://recruit.hkfew.org.hk/jobs/%e3%80%90%e5%85%bc%e8%81%b7-%e5%85%a8%e8%81%b7%e3%80%91%e5%b0%8f%e5%ad%b8-%e4%b8%ad%e5%ad%b8%e8%a3%9c%e7%bf%92%e7%b7%9a%e4%b8%8a%e5%b0%8e%e5%b8%ab/ +8b1d6948640b5ab5ac65c611791c5ba3,Smart Coaching Service,Ben Yim,60507054,smartcshk@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e5%b0%8e%e5%b8%ab/ +797e1ff8dac9dfbd739a0537e3c6652c,啟教,Miss Tsang,52794773,Inspireducationhk@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad-%e5%b0%88%e7%a7%91%e8%80%81%e5%b8%ab/ +d7cc0930e185c319b58f7abda4f7d6b2,小天地中心,方小姐,98679638,fongricky84@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab/ +1413255756fcf084bc978d8f092c489c,Casual Aace Learning Centre,Manson Kwok,56693000,casualace.lc@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e5%b0%88%e7%a7%91%e5%b0%8e%e5%b8%ab/ +f06e10c4c7c28d1398371fb7d72ed76f,嘉諾撒聖方濟各書院,Ms. Chan,25872700,recruit@mail.sfcc.edu.hk,https://recruit.hkfew.org.hk/jobs/ole-music-tutor-part-time/ +2b7857691c3b0dc53a9aa9f9e18eea4c,Good Hope School,Ms. Sonia Wong,23210250(ext.22),recruitment@ghs.edu.hk,https://recruit.hkfew.org.hk/jobs/athletics-sprinting-hurdling-coach-2/ +c59ba9b980ed46641f9cdbdd1132fe19,地利亞修女紀念學校(吉利徑),Ms. FU Ka Yan,27415239,recruitment@dmsgp.edu.hk,https://recruit.hkfew.org.hk/jobs/part-time-tutorial-tutors-and-coaches-of-various-activities/ +2daab7cfa0e02ee067065c5b2ee9609d,驕陽教育中心 Jiao Yang Education Centre,Miss Chan,23218487,jyrecruit@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e5%b0%8f%e5%ad%b8%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e8%a3%9c%e7%bf%92%e5%b0%8e%e5%b8%ab-full-time-tutor-part-time-tutor/ +591fe032128558b8f97a19ee79acfcd0,英藝小學,梁小姐,26633311,zenithhr@zenith.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%95%b8%e5%ad%b8%e6%95%99%e5%b8%ab-2/ +db305c3f36a7d522108539d0249b177b,才藝教育Talent Education,Ms Cheng,54060584,talentedu.hk.tw@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e5%b0%8f%e5%ad%b8%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e8%80%81%e5%b8%ab-%e5%b0%88%e7%a7%91%e8%a3%9c%e7%bf%92%e8%80%81%e5%b8%ab/ +f19efbe4eff41abd964f9e2df96f439f,Popular Learning Center (Lai Chi Kok),Helen Lau,98073009fax,popularlearning.kt@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e6%95%99%e5%ae%a4%e5%b0%8e%e5%b8%ab-%e4%b8%ad%e6%96%87-%e8%8b%b1%e6%96%87%ef%bc%89/ +f5d75503f2708089b3343dc0623447f7,藝柏琴行,梁小姐,27560712,puiyee.l@yahoo.com,https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e8%8b%b1%e8%aa%9e%e6%8b%bc%e9%9f%b3%e5%b0%8e%e5%b8%ab/ +563b2793093e95642fb563081fcd9d58,上水宣道小學,蕭小姐,26702257,recruit@apsss.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-6/ +3d14124671c94b2c4049618cb11b3021,嘉諾撒培德書院,黎小姐,25540780,ptcc@ptcc.edu.hk,https://recruit.hkfew.org.hk/jobs/%e8%8b%b1%e6%96%87%e7%a7%91%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab-%e6%98%9f%e6%9c%9f%e5%85%ad-english-language-tutor-saturday-2022-2023/ +8cc6ee79d035cdc76e4e4bb04db9a39d,腦潛能教育有限公司,林小姐,97573889,ylbrainpower@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%b9%bc%e5%85%92%e5%b0%8e%e5%b8%ab-17/ +7c3c3978684ed9d4d65951d3f47539b9,JL Music Academy,李小姐,63909108,missli_pianomusic@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e6%a8%82%e5%99%a8%e5%b0%8e%e5%b8%ab/ +056ecd2c704f1b2a0dc427a8026fc17b,倫敦卓越書院 (沙田),Mr. CHENG,23656093,examsinfo@excellondoncollege.edu.hk,https://recruit.hkfew.org.hk/jobs/%e7%89%a9%e7%90%86%e5%b0%8e%e5%b8%ab/ +6ad8efa2ce13626ad954dc43a817e483,Popular Learning (Tai Po) Limited 大眾教室(大埔),Miss Wong,98338348,popularlearningtp@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e5%b0%88%e7%a7%91%e5%b0%8e%e5%b8%ab-%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7/ +20bb5664f05aa04cc5ad210071714125,/,薛校長,27707776,jphkghr@gmail.com,https://recruit.hkfew.org.hk/jobs/%e4%b8%ad%e6%96%87%e5%b0%8e%e5%b8%ab-4/ +7089392094e3577958cee70897348c98,長沙灣天主教英文中學,李先生,27415034,contact@cswcss.edu.hk,https://recruit.hkfew.org.hk/jobs/flute-class-tutor/ +ed3747f76ed7c8dda4184b5355e1c3bf,Indigo Kids,Miss Lo,57025777,indigo_kids.adm@hotmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e4%b8%ad%e5%bf%83%e5%b0%8e%e5%b8%ab%e5%85%83%e6%9c%97%e5%8d%80/ +1d4c25d61a00785d661aaa6a429fb59e,東華三院甲寅年總理中學,梁小姐,26727395,mail@twghkyds.edu.hk,https://recruit.hkfew.org.hk/jobs/%e5%a4%a7%e6%8f%90%e7%90%b4%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ +7a95247501ea2635e153937aafc978f8,瑪利灣學校,陳小姐,25540168,recruit@marycove.edu.hk,https://recruit.hkfew.org.hk/jobs/%e6%9a%91%e6%9c%9f%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab/ +9cef6f205c943eb756dd84520573058e,Joyful art centre,Miss wong,60265103,joyfulart777@yahoo.com.hi,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7%e8%a6%96%e8%a6%ba%e8%97%9d%e8%a1%93%e5%b0%8e%e5%b8%ab-2/ +1c0efcb09110298982489de9bee9d2f8,聖公會聖三一堂中學,林小姐,27144137,office@skhhtcss.edu.hk,https://recruit.hkfew.org.hk/jobs/english-drama-teacherpart-time/ +6e2f2de501c6710df7807e5006480f7f,香港遊樂場協會賽馬會上葵涌青少年綜合服務中心,何小姐,24243043,ritaho@hkpa.hk,https://recruit.hkfew.org.hk/jobs/%e6%8b%9b%e8%81%98%e8%91%b5%e6%b6%8cngo%e5%8a%9f%e8%aa%b2%e8%bc%94%e5%b0%8e%e7%8f%ad%e5%b0%8e%e5%b8%ab/ +513328af8959c478ba231b2446a72c7f,東華三院馬振玉紀念中學,黎兆添老師,24439833,dadadee1234@yahoo.com.hk,https://recruit.hkfew.org.hk/jobs/%e6%97%a5%e6%96%87%e5%9f%ba%e7%a4%8e%e8%aa%b2%e7%a8%8b%e5%b0%8e%e5%b8%ab%e4%b8%ad%e5%9b%9b%e3%80%81%e4%ba%94%e7%b4%9a/ +a12e8497f0f2125d2c4004944cec0d26,大衆教室(薈學坊)有限公司,Mr. Ng,98595581,plcsmarta3c@gmail.com,https://recruit.hkfew.org.hk/jobs/%e5%85%a8%e8%81%b7-%e5%85%bc%e8%81%b7%e5%b0%8e%e5%b8%ab-3/ +2d19e957cce88295f3761a94e71a614d,香港神託會 生命教育計劃,Jade LAM,26371866,life@stewards.hk,https://recruit.hkfew.org.hk/jobs/%e6%88%b2%e5%8a%87%e5%b0%8e%e5%b8%ab/ +31e6ed32bc99809b3bda75ed37c50744,Anglomile Education Centre,Mr Chan,67199919,hello@anglomile.com,https://recruit.hkfew.org.hk/jobs/%e5%85%bc%e8%81%b7%e8%8b%b1%e8%aa%9e%e7%8f%ad%e5%b0%8e%e5%b8%ab-2/ diff --git a/src/YuanjiajiaQl.php b/src/YuanjiajiaQl.php new file mode 100644 index 0000000..f04ca14 --- /dev/null +++ b/src/YuanjiajiaQl.php @@ -0,0 +1,178 @@ +html($result); + $urls = $qlList->find('article')->attrs('data-url')->all(); + + + foreach($urls as $url){ + + $itemCount = $itemCount + 1; + $user = null; + $qlDetail = new QueryList(); + $qlTemp = new QueryList(); + + + // $curl = curl_init(); + // curl_setopt($curl,CURLOPT_RETURNTRANSFER,true); + // curl_setopt($curl,CURLOPT_URL,$url); + // $result = curl_exec($curl); + // $info = curl_getinfo($curl); + // if($info["http_code"] !== 200){ + // echo "Http 请求错误: ".$url."。程序已经终止\n"; + // break; + // } + // curl_close($curl); + + + try { + $qlDetail = $qlDetail->get($url); + } catch (RequestException $e) { + echo "Http 请求错误: ".$e->message."url: ".$listUrl.$page."。跳过当前链接\n"; + continue; + } + $ul = $qlDetail->find('div.video-gallery-fields ul li')->htmls()->all(); + + + foreach($ul as $li){ + $qlTemp->html($li); + + if (preg_match("/(value-_noo_job_field_job_school_name cf-text-value){1}/", $qlTemp->find('span')->attr('class')) == 1) { + + $user['companyName'] = preg_replace("/[,]+/", ' ', $qlTemp->find('span')->text()); + + } else if (preg_match("/(value-_noo_job_field_contact cf-text-value){1}/", $qlTemp->find('span')->attr('class')) == 1) { + + $user['contactPerson'] = preg_replace("/[,]+/", ' or ', $qlTemp->find('span')->text()); + + } else if (preg_match("/(value-_noo_job_field_phone cf-text-value){1}/", $qlTemp->find('span')->attr('class')) == 1) { + + $user['tel'] = preg_replace("/[,]+/", ' or ', $qlTemp->find('span')->text()); + $user['tel'] = preg_replace("/[\s]+/", '', $user['tel']); + $user['tel'] = preg_replace("/[-]+/", '', $user['tel']); + + } else if (preg_match("/(value-_noo_job_field_job_email cf-email-value){1}/", $qlTemp->find('span')->attr('class')) == 1) { + + $user['email'] = $this->deCFEmail($qlTemp->find('span a')->attr('data-cfemail')); + $user['email'] = preg_replace("/[,]+/", ' or ', $user['email']); + $user['email'] = preg_replace("/[\s]+/", '', $user['email']); + + } + $user['url'] = $url; + } + + + if( !empty($user['tel']) && !empty($user['email'])){ + + $user['companyName'] = !empty($user['companyName']) ? $user['companyName'] : '/'; + $user['contactPerson'] = !empty($user['contactPerson']) ? $user['contactPerson'] : '/'; + + $usreInfo = $user['companyName'].','.$user['contactPerson'].','.str_replace(' ','',$user['tel']).','.$user['email'].','.$user['url']; + $hashKey = hash('md5', str_replace(' ','',$user['tel']).','.$user['email']); + + if( !array_key_exists($hashKey, $users)) + { + echo '当前第 '.$page.' 页: 第 '.$itemCount.' 个,有效个数: '.++$validCount."\n"; + echo '机构: '.$user['companyName']."\n"; + echo '联系人: '.$user['contactPerson']."\n"; + echo 'tel: '.str_replace(' ','',$user['tel'])."\n"; + echo 'email: '.$user['email']."\n"; + echo 'url: '.$user['url']."\n\n"; + $users[$hashKey] = $usreInfo; + $fpRecruitInfo = fopen("./qlOut/recruit_info.xls",'a+'); + fwrite($fpRecruitInfo, $hashKey.','.$usreInfo."\n"); + fclose($fpRecruitInfo); + }else{ + $equalUrl = $users[$hashKey]; + $errLog = '数据无效(2): 当前第 '.$page.' 页: 第 '.$itemCount.' 个无效。与 '.$equalUrl.' 具有相同的 tel 或 email。 url: '.$url."\n\n"; + echo $errLog; + $fpErrLog = fopen("./qlOut/errLog.txt",'a+'); + fwrite($fpErrLog, $errLog."\n"); + fclose($fpErrLog); + } + + // break; + }else{ + $errLog = '数据无效(1): 当前第 '.$page." 页: 第 ".$itemCount." 个无效。没有有效的 tel 或 email。 url: ".$url."\n\n"; + echo $errLog; + $fpErrLog = fopen("./qlOut/errLog.txt",'a+'); + fwrite($fpErrLog, $errLog."\n"); + fclose($fpErrLog); + } + sleep(10); + } + // break; + + $fpQueryListLog = fopen("./qlOut/QueryListLog.txt",'w+'); + fwrite($fpQueryListLog, $page."\n"); + fclose($fpQueryListLog); + } + } + + + + /** + * 解密email + */ + function deCFEmail($encode) + { + $k = hexdec(substr($encode, 0, 2)); + for ($i = 2, $m = ''; $i < strlen($encode) - 1; $i += 2) { + $m .= chr(hexdec(substr($encode, $i, 2)) ^ $k); + } + return $m; + } +} diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..a57d145 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,12 @@ +realpath = realpath($opened_path) ?: $opened_path; + $opened_path = $this->realpath; + $this->handle = fopen($this->realpath, $mode); + $this->position = 0; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) { + include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'); + exit(0); + } +} + +include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server'; diff --git a/vendor/cache/adapter-common/AbstractCachePool.php b/vendor/cache/adapter-common/AbstractCachePool.php new file mode 100644 index 0000000..de7d2bc --- /dev/null +++ b/vendor/cache/adapter-common/AbstractCachePool.php @@ -0,0 +1,559 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +use Cache\Adapter\Common\Exception\CacheException; +use Cache\Adapter\Common\Exception\CachePoolException; +use Cache\Adapter\Common\Exception\InvalidArgumentException; +use Psr\Cache\CacheItemInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Psr\SimpleCache\CacheInterface; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + */ +abstract class AbstractCachePool implements PhpCachePool, LoggerAwareInterface, CacheInterface +{ + const SEPARATOR_TAG = '!'; + + /** + * @type LoggerInterface + */ + private $logger; + + /** + * @type PhpCacheItem[] deferred + */ + protected $deferred = []; + + /** + * @param PhpCacheItem $item + * @param int|null $ttl seconds from now + * + * @return bool true if saved + */ + abstract protected function storeItemInCache(PhpCacheItem $item, $ttl); + + /** + * Fetch an object from the cache implementation. + * + * If it is a cache miss, it MUST return [false, null, [], null] + * + * @param string $key + * + * @return array with [isHit, value, tags[], expirationTimestamp] + */ + abstract protected function fetchObjectFromCache($key); + + /** + * Clear all objects from cache. + * + * @return bool false if error + */ + abstract protected function clearAllObjectsFromCache(); + + /** + * Remove one object from cache. + * + * @param string $key + * + * @return bool + */ + abstract protected function clearOneObjectFromCache($key); + + /** + * Get an array with all the values in the list named $name. + * + * @param string $name + * + * @return array + */ + abstract protected function getList($name); + + /** + * Remove the list. + * + * @param string $name + * + * @return bool + */ + abstract protected function removeList($name); + + /** + * Add a item key on a list named $name. + * + * @param string $name + * @param string $key + */ + abstract protected function appendListItem($name, $key); + + /** + * Remove an item from the list. + * + * @param string $name + * @param string $key + */ + abstract protected function removeListItem($name, $key); + + /** + * Make sure to commit before we destruct. + */ + public function __destruct() + { + $this->commit(); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $this->validateKey($key); + if (isset($this->deferred[$key])) { + /** @type CacheItem $item */ + $item = clone $this->deferred[$key]; + $item->moveTagsToPrevious(); + + return $item; + } + + $func = function () use ($key) { + try { + return $this->fetchObjectFromCache($key); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + }; + + return new CacheItem($key, $func); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []) + { + $items = []; + foreach ($keys as $key) { + $items[$key] = $this->getItem($key); + } + + return $items; + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + try { + return $this->getItem($key)->isHit(); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // Clear the deferred items + $this->deferred = []; + + try { + return $this->clearAllObjectsFromCache(); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + try { + return $this->deleteItems([$key]); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + $deleted = true; + foreach ($keys as $key) { + $this->validateKey($key); + + // Delete form deferred + unset($this->deferred[$key]); + + // We have to commit here to be able to remove deferred hierarchy items + $this->commit(); + $this->preRemoveItem($key); + + if (!$this->clearOneObjectFromCache($key)) { + $deleted = false; + } + } + + return $deleted; + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof PhpCacheItem) { + $e = new InvalidArgumentException('Cache items are not transferable between pools. Item MUST implement PhpCacheItem.'); + $this->handleException($e, __FUNCTION__); + } + + $this->removeTagEntries($item); + $this->saveTags($item); + $timeToLive = null; + if (null !== $timestamp = $item->getExpirationTimestamp()) { + $timeToLive = $timestamp - time(); + + if ($timeToLive < 0) { + return $this->deleteItem($item->getKey()); + } + } + + try { + return $this->storeItemInCache($item, $timeToLive); + } catch (\Exception $e) { + $this->handleException($e, __FUNCTION__); + } + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + $this->deferred[$item->getKey()] = $item; + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + $saved = true; + foreach ($this->deferred as $item) { + if (!$this->save($item)) { + $saved = false; + } + } + $this->deferred = []; + + return $saved; + } + + /** + * @param string $key + * + * @throws InvalidArgumentException + */ + protected function validateKey($key) + { + if (!is_string($key)) { + $e = new InvalidArgumentException(sprintf( + 'Cache key must be string, "%s" given', + gettype($key) + )); + $this->handleException($e, __FUNCTION__); + } + if (!isset($key[0])) { + $e = new InvalidArgumentException('Cache key cannot be an empty string'); + $this->handleException($e, __FUNCTION__); + } + if (preg_match('|[\{\}\(\)/\\\@\:]|', $key)) { + $e = new InvalidArgumentException(sprintf( + 'Invalid key: "%s". The key contains one or more characters reserved for future extension: {}()/\@:', + $key + )); + $this->handleException($e, __FUNCTION__); + } + } + + /** + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger): void + { + $this->logger = $logger; + } + + /** + * Logs with an arbitrary level if the logger exists. + * + * @param mixed $level + * @param string $message + * @param array $context + */ + protected function log($level, $message, array $context = []) + { + if ($this->logger !== null) { + $this->logger->log($level, $message, $context); + } + } + + /** + * Log exception and rethrow it. + * + * @param \Exception $e + * @param string $function + * + * @throws CachePoolException + */ + private function handleException(\Exception $e, $function) + { + $level = 'alert'; + if ($e instanceof InvalidArgumentException) { + $level = 'warning'; + } + + $this->log($level, $e->getMessage(), ['exception' => $e]); + if (!$e instanceof CacheException) { + $e = new CachePoolException(sprintf('Exception thrown when executing "%s". ', $function), 0, $e); + } + + throw $e; + } + + /** + * @param array $tags + * + * @return bool + */ + public function invalidateTags(array $tags) + { + $itemIds = []; + foreach ($tags as $tag) { + $itemIds = array_merge($itemIds, $this->getList($this->getTagKey($tag))); + } + + // Remove all items with the tag + $success = $this->deleteItems($itemIds); + + if ($success) { + // Remove the tag list + foreach ($tags as $tag) { + $this->removeList($this->getTagKey($tag)); + $l = $this->getList($this->getTagKey($tag)); + } + } + + return $success; + } + + public function invalidateTag($tag) + { + return $this->invalidateTags([$tag]); + } + + /** + * @param PhpCacheItem $item + */ + protected function saveTags(PhpCacheItem $item) + { + $tags = $item->getTags(); + foreach ($tags as $tag) { + $this->appendListItem($this->getTagKey($tag), $item->getKey()); + } + } + + /** + * Removes the key form all tag lists. When an item with tags is removed + * we MUST remove the tags. If we fail to remove the tags a new item with + * the same key will automatically get the previous tags. + * + * @param string $key + * + * @return $this + */ + protected function preRemoveItem($key) + { + $item = $this->getItem($key); + $this->removeTagEntries($item); + + return $this; + } + + /** + * @param PhpCacheItem $item + */ + private function removeTagEntries(PhpCacheItem $item) + { + $tags = $item->getPreviousTags(); + foreach ($tags as $tag) { + $this->removeListItem($this->getTagKey($tag), $item->getKey()); + } + } + + /** + * @param string $tag + * + * @return string + */ + protected function getTagKey($tag) + { + return 'tag'.self::SEPARATOR_TAG.$tag; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + $item = $this->getItem($key); + if (!$item->isHit()) { + return $default; + } + + return $item->get(); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $item = $this->getItem($key); + $item->set($value); + $item->expiresAfter($ttl); + + return $this->save($item); + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + return $this->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + if (!is_array($keys)) { + if (!$keys instanceof \Traversable) { + throw new InvalidArgumentException('$keys is neither an array nor Traversable'); + } + + // Since we need to throw an exception if *any* key is invalid, it doesn't + // make sense to wrap iterators or something like that. + $keys = iterator_to_array($keys, false); + } + + $items = $this->getItems($keys); + + return $this->generateValues($default, $items); + } + + /** + * @param $default + * @param $items + * + * @return \Generator + */ + private function generateValues($default, $items) + { + foreach ($items as $key => $item) { + /** @type $item CacheItemInterface */ + if (!$item->isHit()) { + yield $key => $default; + } else { + yield $key => $item->get(); + } + } + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + if (!is_array($values)) { + if (!$values instanceof \Traversable) { + throw new InvalidArgumentException('$values is neither an array nor Traversable'); + } + } + + $keys = []; + $arrayValues = []; + foreach ($values as $key => $value) { + if (is_int($key)) { + $key = (string) $key; + } + $this->validateKey($key); + $keys[] = $key; + $arrayValues[$key] = $value; + } + + $items = $this->getItems($keys); + $itemSuccess = true; + foreach ($items as $key => $item) { + $item->set($arrayValues[$key]); + + try { + $item->expiresAfter($ttl); + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + $itemSuccess = $itemSuccess && $this->saveDeferred($item); + } + + return $itemSuccess && $this->commit(); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys) + { + if (!is_array($keys)) { + if (!$keys instanceof \Traversable) { + throw new InvalidArgumentException('$keys is neither an array nor Traversable'); + } + + // Since we need to throw an exception if *any* key is invalid, it doesn't + // make sense to wrap iterators or something like that. + $keys = iterator_to_array($keys, false); + } + + return $this->deleteItems($keys); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return $this->hasItem($key); + } +} diff --git a/vendor/cache/adapter-common/CacheItem.php b/vendor/cache/adapter-common/CacheItem.php new file mode 100644 index 0000000..47e68c3 --- /dev/null +++ b/vendor/cache/adapter-common/CacheItem.php @@ -0,0 +1,269 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +use Cache\Adapter\Common\Exception\InvalidArgumentException; +use Cache\TagInterop\TaggableCacheItemInterface; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + */ +class CacheItem implements PhpCacheItem +{ + /** + * @type array + */ + private $prevTags = []; + + /** + * @type array + */ + private $tags = []; + + /** + * @type \Closure + */ + private $callable; + + /** + * @type string + */ + private $key; + + /** + * @type mixed + */ + private $value; + + /** + * The expiration timestamp is the source of truth. This is the UTC timestamp + * when the cache item expire. A value of zero means it never expires. A nullvalue + * means that no expiration is set. + * + * @type int|null + */ + private $expirationTimestamp = null; + + /** + * @type bool + */ + private $hasValue = false; + + /** + * @param string $key + * @param \Closure|bool $callable or boolean hasValue + */ + public function __construct($key, $callable = null, $value = null) + { + $this->key = $key; + + if ($callable === true) { + $this->hasValue = true; + $this->value = $value; + } elseif ($callable !== false) { + // This must be a callable or null + $this->callable = $callable; + } + } + + /** + * {@inheritdoc} + */ + public function getKey() + { + return $this->key; + } + + /** + * {@inheritdoc} + */ + public function set($value) + { + $this->value = $value; + $this->hasValue = true; + $this->callable = null; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get() + { + if (!$this->isHit()) { + return; + } + + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function isHit() + { + $this->initialize(); + + if (!$this->hasValue) { + return false; + } + + if ($this->expirationTimestamp !== null) { + return $this->expirationTimestamp > time(); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getExpirationTimestamp() + { + return $this->expirationTimestamp; + } + + /** + * {@inheritdoc} + */ + public function expiresAt($expiration) + { + if ($expiration instanceof \DateTimeInterface) { + $this->expirationTimestamp = $expiration->getTimestamp(); + } elseif (is_int($expiration) || null === $expiration) { + $this->expirationTimestamp = $expiration; + } else { + throw new InvalidArgumentException('Cache item ttl/expiresAt must be of type integer or \DateTimeInterface.'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function expiresAfter($time) + { + if ($time === null) { + $this->expirationTimestamp = null; + } elseif ($time instanceof \DateInterval) { + $date = new \DateTime(); + $date->add($time); + $this->expirationTimestamp = $date->getTimestamp(); + } elseif (is_int($time)) { + $this->expirationTimestamp = time() + $time; + } else { + throw new InvalidArgumentException('Cache item ttl/expiresAfter must be of type integer or \DateInterval.'); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPreviousTags() + { + $this->initialize(); + + return $this->prevTags; + } + + /** + * {@inheritdoc} + */ + public function getTags() + { + return $this->tags; + } + + /** + * {@inheritdoc} + */ + public function setTags(array $tags) + { + $this->tags = []; + $this->tag($tags); + + return $this; + } + + /** + * Adds a tag to a cache item. + * + * @param string|string[] $tags A tag or array of tags + * + * @throws InvalidArgumentException When $tag is not valid. + * + * @return TaggableCacheItemInterface + */ + private function tag($tags) + { + $this->initialize(); + + if (!is_array($tags)) { + $tags = [$tags]; + } + foreach ($tags as $tag) { + if (!is_string($tag)) { + throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', is_object($tag) ? get_class($tag) : gettype($tag))); + } + if (isset($this->tags[$tag])) { + continue; + } + if (!isset($tag[0])) { + throw new InvalidArgumentException('Cache tag length must be greater than zero'); + } + if (isset($tag[strcspn($tag, '{}()/\@:')])) { + throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag)); + } + $this->tags[$tag] = $tag; + } + + return $this; + } + + /** + * If callable is not null, execute it an populate this object with values. + */ + private function initialize() + { + if ($this->callable !== null) { + // $func will be $adapter->fetchObjectFromCache(); + $func = $this->callable; + $result = $func(); + $this->hasValue = $result[0]; + $this->value = $result[1]; + $this->prevTags = isset($result[2]) ? $result[2] : []; + $this->expirationTimestamp = null; + + if (isset($result[3]) && is_int($result[3])) { + $this->expirationTimestamp = $result[3]; + } + + $this->callable = null; + } + } + + /** + * @internal This function should never be used and considered private. + * + * Move tags from $tags to $prevTags + */ + public function moveTagsToPrevious() + { + $this->prevTags = $this->tags; + $this->tags = []; + } +} diff --git a/vendor/cache/adapter-common/Changelog.md b/vendor/cache/adapter-common/Changelog.md new file mode 100644 index 0000000..5a82188 --- /dev/null +++ b/vendor/cache/adapter-common/Changelog.md @@ -0,0 +1,73 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## 1.3.0 + +* Support for PHP 8.1 +* Drop support for PHP < 7.4 +* Allow psr/cache: ^1.0 || ^2.0 + +## 1.2.0 + +### Added + +* Support for PHP 8 + +## 1.1.0 + +### Added + +- Support for storing binary data + +### Fixed + +- Issue with one character variables + +### Changed + +- Tests are now extending `PHPUnit\Framework\TestCase` + +## 1.0.0 + +* No changes since 0.4.0. + +## 0.4.0 + +### Added + +* `AbstractCachePool` has 4 new abstract methods: `getList`, `removeList`, `appendListItem` and `removeListItem`. +* `AbstractCachePool::invalidateTags` and `AbstractCachePool::invalidateTags` +* Added interfaces for our items and pools `PhpCachePool` and `PhpCacheItem` +* Trait to help adapters to support tags. `TagSupportWithArray`. + +### Changed + +* First parameter to `AbstractCachePool::storeItemInCache` must be a `PhpCacheItem`. +* Return value from `AbstractCachePool::fetchObjectFromCache` must be a an array with 4 values. Added expiration timestamp. +* `HasExpirationDateInterface` is replaced by `HasExpirationTimestampInterface` +* We do not work with `\DateTime` internally anymore. We work with timestamps. + +## 0.3.3 + +### Fixed + +* Bugfix when you fetch data from the cache storage that was saved as "non-tagging item" but fetch as a tagging item. + +## 0.3.2 + +### Added + +* Cache pools do implement `LoggerAwareInterface` + +## 0.3.0 + +### Changed + +* The `AbstractCachePool` does not longer implement `TaggablePoolInterface`. However, the `CacheItem` does still implement `TaggableItemInterface`. +* `CacheItem::getKeyFromTaggedKey` has been removed +* The `CacheItem`'s second parameter is a callable that must return an array with 3 elements; [`hasValue`, `value`, `tags`]. + +## 0.2.0 + +* No changelog before this version diff --git a/vendor/cache/adapter-common/Exception/CacheException.php b/vendor/cache/adapter-common/Exception/CacheException.php new file mode 100644 index 0000000..54fbb11 --- /dev/null +++ b/vendor/cache/adapter-common/Exception/CacheException.php @@ -0,0 +1,23 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common\Exception; + +use Psr\Cache\CacheException as CacheExceptionInterface; + +/** + * A base exception. All exceptions in this organization will extend this exception. + * + * @author Tobias Nyholm + */ +abstract class CacheException extends \RuntimeException implements CacheExceptionInterface +{ +} diff --git a/vendor/cache/adapter-common/Exception/CachePoolException.php b/vendor/cache/adapter-common/Exception/CachePoolException.php new file mode 100644 index 0000000..c0b7e59 --- /dev/null +++ b/vendor/cache/adapter-common/Exception/CachePoolException.php @@ -0,0 +1,21 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common\Exception; + +/** + * If an exception is caused by a pool or by the cache storage. + * + * @author Tobias Nyholm + */ +class CachePoolException extends CacheException +{ +} diff --git a/vendor/cache/adapter-common/Exception/InvalidArgumentException.php b/vendor/cache/adapter-common/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..e3cc8f4 --- /dev/null +++ b/vendor/cache/adapter-common/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common\Exception; + +use Psr\Cache\InvalidArgumentException as CacheInvalidArgumentException; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentException; + +class InvalidArgumentException extends CacheException implements CacheInvalidArgumentException, SimpleCacheInvalidArgumentException +{ +} diff --git a/vendor/cache/adapter-common/HasExpirationTimestampInterface.php b/vendor/cache/adapter-common/HasExpirationTimestampInterface.php new file mode 100644 index 0000000..22f0adf --- /dev/null +++ b/vendor/cache/adapter-common/HasExpirationTimestampInterface.php @@ -0,0 +1,26 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +/** + * @author Aaron Scherer + * @author Tobias Nyholm + */ +interface HasExpirationTimestampInterface +{ + /** + * The timestamp when the object expires. + * + * @return int|null + */ + public function getExpirationTimestamp(); +} diff --git a/vendor/cache/adapter-common/JsonBinaryArmoring.php b/vendor/cache/adapter-common/JsonBinaryArmoring.php new file mode 100644 index 0000000..7e3d91d --- /dev/null +++ b/vendor/cache/adapter-common/JsonBinaryArmoring.php @@ -0,0 +1,68 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +/** + * This trait provides common routines for safely encoding binary and non-UTF8 data in + * JSON. This is needed for components that use JSON natively (currently, the MongoDB + * adapter and EncryptedCachePool). + * + * @author Stephen Clouse + */ +trait JsonBinaryArmoring +{ + private static $ESCAPE_JSON_CHARACTERS = [ + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0A", "\x0B", "\x0C", "\x0D", "\x0E", "\x0F", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1A", "\x1B", "\x1C", "\x1D", "\x1E", "\x1F", + ]; + + private static $ENCODED_JSON_CHARACTERS = [ + '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', + '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u000E', '\u000F', + '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', + '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', + ]; + + /** + * Armor a value going into a JSON document. + * + * @param string $value + * + * @return string + */ + protected static function jsonArmor($value) + { + return str_replace( + static::$ESCAPE_JSON_CHARACTERS, + static::$ENCODED_JSON_CHARACTERS, + utf8_encode($value) + ); + } + + /** + * De-armor a value from a JSON document. + * + * @param string $value + * + * @return string + */ + protected static function jsonDeArmor($value) + { + return utf8_decode(str_replace( + static::$ENCODED_JSON_CHARACTERS, + static::$ESCAPE_JSON_CHARACTERS, + $value + )); + } +} diff --git a/vendor/cache/adapter-common/LICENSE b/vendor/cache/adapter-common/LICENSE new file mode 100644 index 0000000..82f8fee --- /dev/null +++ b/vendor/cache/adapter-common/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/adapter-common/PhpCacheItem.php b/vendor/cache/adapter-common/PhpCacheItem.php new file mode 100644 index 0000000..8d6ed5e --- /dev/null +++ b/vendor/cache/adapter-common/PhpCacheItem.php @@ -0,0 +1,32 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +use Cache\TagInterop\TaggableCacheItemInterface; + +/** + * @author Tobias Nyholm + */ +interface PhpCacheItem extends HasExpirationTimestampInterface, TaggableCacheItemInterface +{ + /** + * Get the current tags. These are not the same tags as getPrevious tags. This + * is the tags that has been added to the item after the item was fetched from + * the cache storage. + * + * WARNING: This is generally not the function you want to use. Please see + * `getPreviousTags`. + * + * @return array + */ + public function getTags(); +} diff --git a/vendor/cache/adapter-common/PhpCachePool.php b/vendor/cache/adapter-common/PhpCachePool.php new file mode 100644 index 0000000..5ccfb67 --- /dev/null +++ b/vendor/cache/adapter-common/PhpCachePool.php @@ -0,0 +1,34 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +use Cache\TagInterop\TaggableCacheItemPoolInterface; + +/** + * @author Tobias Nyholm + */ +interface PhpCachePool extends TaggableCacheItemPoolInterface +{ + /** + * {@inheritdoc} + * + * @return PhpCacheItem + */ + public function getItem($key); + + /** + * {@inheritdoc} + * + * @return array|\Traversable|PhpCacheItem[] + */ + public function getItems(array $keys = []); +} diff --git a/vendor/cache/adapter-common/README.md b/vendor/cache/adapter-common/README.md new file mode 100644 index 0000000..3ce3480 --- /dev/null +++ b/vendor/cache/adapter-common/README.md @@ -0,0 +1,15 @@ +# Common PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/adapter-common/v/stable)](https://packagist.org/packages/cache/adapter-common) +[![codecov.io](https://codecov.io/github/php-cache/adapter-common/coverage.svg?branch=master)](https://codecov.io/github/php-cache/adapter-common?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/adapter-common/downloads)](https://packagist.org/packages/cache/adapter-common) +[![Monthly Downloads](https://poser.pugx.org/cache/adapter-common/d/monthly.png)](https://packagist.org/packages/cache/adapter-common) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This repository contains shared classes and interfaces used by the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/adapter-common/TagSupportWithArray.php b/vendor/cache/adapter-common/TagSupportWithArray.php new file mode 100644 index 0000000..81859d2 --- /dev/null +++ b/vendor/cache/adapter-common/TagSupportWithArray.php @@ -0,0 +1,88 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Common; + +/** + * This trait could be used by adapters that do not have a native support for lists. + * + * @author Tobias Nyholm + */ +trait TagSupportWithArray +{ + /** + * Get a value from the storage. + * + * @param string $name + * + * @return mixed + */ + abstract public function getDirectValue($name); + + /** + * Set a value to the storage. + * + * @param string $name + * @param mixed $value + */ + abstract public function setDirectValue($name, $value); + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $value) + { + $data = $this->getDirectValue($name); + if (!is_array($data)) { + $data = []; + } + $data[] = $value; + $this->setDirectValue($name, $data); + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + $data = $this->getDirectValue($name); + if (!is_array($data)) { + $data = []; + } + + return $data; + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + $this->setDirectValue($name, []); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + $data = $this->getList($name); + foreach ($data as $i => $value) { + if ($key === $value) { + unset($data[$i]); + } + } + + return $this->setDirectValue($name, $data); + } +} diff --git a/vendor/cache/adapter-common/composer.json b/vendor/cache/adapter-common/composer.json new file mode 100644 index 0000000..c773462 --- /dev/null +++ b/vendor/cache/adapter-common/composer.json @@ -0,0 +1,55 @@ +{ + "name": "cache/adapter-common", + "description": "Common classes for PSR-6 adapters", + "license": "MIT", + "type": "library", + "keywords": [ + "cache", + "psr-6", + "tag" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "homepage": "http://www.php-cache.com/en/latest/", + "require": { + "php": ">=7.4", + "cache/tag-interop": "^1.0", + "psr/cache": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "cache/integration-tests": "^0.17", + "phpunit/phpunit": "^7.5.20 || ^9.5.10" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Common\\": "" + } + }, + "autoload-dev": { + "psr-4": { + "Cache\\Adapter\\Common\\Tests\\": "Tests/" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/vendor/cache/filesystem-adapter/Changelog.md b/vendor/cache/filesystem-adapter/Changelog.md new file mode 100644 index 0000000..bd1f902 --- /dev/null +++ b/vendor/cache/filesystem-adapter/Changelog.md @@ -0,0 +1,64 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## UNRELEASED + +## 1.2.0 + +* Support for PHP 8.1 +* Drop support for PHP < 7.4 +* Allow psr/cache: ^1.0 || ^2.0 + +## 1.1.0 + +### Added + +* Support for PHP 8 + +### Changed + +* Use `League\Flysystem\FilesystemInterface` instead of concrete `League\Flysystem\Filesystem` class + +## 1.0.0 + +* No changes since 0.4.0 + +## 0.4.0 + +### Added + +* Support for the new `TaggableCacheItemPoolInterface`. +* Support for PSR-16 SimpleCache + +### Changed + +* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. + +### Removed + +* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` +* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` +* `CacheItem::addTag()`. Use `CacheItem::setTags()` + +## 0.3.3 + +### Fixed + +* Race condition in `fetchObjectFromCache`. + +## 0.3.2 + +### Changed + +* Using `Filesystem::update` instead of `Filesystem::delete` and `Filesystem::write`. + +## 0.3.1 + +### Added + +* Add ability to change cache path in FilesystemCachePool + +## 0.3.0 + +* No changelog before this version diff --git a/vendor/cache/filesystem-adapter/FilesystemCachePool.php b/vendor/cache/filesystem-adapter/FilesystemCachePool.php new file mode 100644 index 0000000..065519d --- /dev/null +++ b/vendor/cache/filesystem-adapter/FilesystemCachePool.php @@ -0,0 +1,213 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\Adapter\Filesystem; + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Common\Exception\InvalidArgumentException; +use Cache\Adapter\Common\PhpCacheItem; +use League\Flysystem\FileExistsException; +use League\Flysystem\FileNotFoundException; +use League\Flysystem\FilesystemInterface; + +/** + * @author Tobias Nyholm + */ +class FilesystemCachePool extends AbstractCachePool +{ + /** + * @type FilesystemInterface + */ + private $filesystem; + + /** + * The folder should not begin nor end with a slash. Example: path/to/cache. + * + * @type string + */ + private $folder; + + /** + * @param FilesystemInterface $filesystem + * @param string $folder + */ + public function __construct(FilesystemInterface $filesystem, $folder = 'cache') + { + $this->folder = $folder; + + $this->filesystem = $filesystem; + $this->filesystem->createDir($this->folder); + } + + /** + * @param string $folder + */ + public function setFolder($folder) + { + $this->folder = $folder; + } + + /** + * {@inheritdoc} + */ + protected function fetchObjectFromCache($key) + { + $empty = [false, null, [], null]; + $file = $this->getFilePath($key); + + try { + $data = @unserialize($this->filesystem->read($file)); + if ($data === false) { + return $empty; + } + } catch (FileNotFoundException $e) { + return $empty; + } + + // Determine expirationTimestamp from data, remove items if expired + $expirationTimestamp = $data[2] ?: null; + if ($expirationTimestamp !== null && time() > $expirationTimestamp) { + foreach ($data[1] as $tag) { + $this->removeListItem($this->getTagKey($tag), $key); + } + $this->forceClear($key); + + return $empty; + } + + return [true, $data[0], $data[1], $expirationTimestamp]; + } + + /** + * {@inheritdoc} + */ + protected function clearAllObjectsFromCache() + { + $this->filesystem->deleteDir($this->folder); + $this->filesystem->createDir($this->folder); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function clearOneObjectFromCache($key) + { + return $this->forceClear($key); + } + + /** + * {@inheritdoc} + */ + protected function storeItemInCache(PhpCacheItem $item, $ttl) + { + $data = serialize( + [ + $item->get(), + $item->getTags(), + $item->getExpirationTimestamp(), + ] + ); + + $file = $this->getFilePath($item->getKey()); + if ($this->filesystem->has($file)) { + // Update file if it exists + return $this->filesystem->update($file, $data); + } + + try { + return $this->filesystem->write($file, $data); + } catch (FileExistsException $e) { + // To handle issues when/if race conditions occurs, we try to update here. + return $this->filesystem->update($file, $data); + } + } + + /** + * @param string $key + * + * @throws InvalidArgumentException + * + * @return string + */ + private function getFilePath($key) + { + if (!preg_match('|^[a-zA-Z0-9_\.! ]+$|', $key)) { + throw new InvalidArgumentException(sprintf('Invalid key "%s". Valid filenames must match [a-zA-Z0-9_\.! ].', $key)); + } + + return sprintf('%s/%s', $this->folder, $key); + } + + /** + * {@inheritdoc} + */ + protected function getList($name) + { + $file = $this->getFilePath($name); + + if (!$this->filesystem->has($file)) { + $this->filesystem->write($file, serialize([])); + } + + return unserialize($this->filesystem->read($file)); + } + + /** + * {@inheritdoc} + */ + protected function removeList($name) + { + $file = $this->getFilePath($name); + $this->filesystem->delete($file); + } + + /** + * {@inheritdoc} + */ + protected function appendListItem($name, $key) + { + $list = $this->getList($name); + $list[] = $key; + + return $this->filesystem->update($this->getFilePath($name), serialize($list)); + } + + /** + * {@inheritdoc} + */ + protected function removeListItem($name, $key) + { + $list = $this->getList($name); + foreach ($list as $i => $item) { + if ($item === $key) { + unset($list[$i]); + } + } + + return $this->filesystem->update($this->getFilePath($name), serialize($list)); + } + + /** + * @param $key + * + * @return bool + */ + private function forceClear($key) + { + try { + return $this->filesystem->delete($this->getFilePath($key)); + } catch (FileNotFoundException $e) { + return true; + } + } +} diff --git a/vendor/cache/filesystem-adapter/LICENSE b/vendor/cache/filesystem-adapter/LICENSE new file mode 100644 index 0000000..82f8fee --- /dev/null +++ b/vendor/cache/filesystem-adapter/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/filesystem-adapter/README.md b/vendor/cache/filesystem-adapter/README.md new file mode 100644 index 0000000..90b4e45 --- /dev/null +++ b/vendor/cache/filesystem-adapter/README.md @@ -0,0 +1,45 @@ +# Filesystem PSR-6 Cache pool +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/filesystem-adapter/v/stable)](https://packagist.org/packages/cache/filesystem-adapter) +[![codecov.io](https://codecov.io/github/php-cache/filesystem-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/filesystem-adapter?branch=master) +[![Total Downloads](https://poser.pugx.org/cache/filesystem-adapter/downloads)](https://packagist.org/packages/cache/filesystem-adapter) +[![Monthly Downloads](https://poser.pugx.org/cache/filesystem-adapter/d/monthly.png)](https://packagist.org/packages/cache/filesystem-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is a PSR-6 cache implementation using Filesystem. It is a part of the PHP Cache organisation. To read about +features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). + +This implementation is using the excellent [Flysystem](http://flysystem.thephpleague.com/). + +### Install + +```bash +composer require cache/filesystem-adapter +``` + +### Use + +To create an instance of `FilesystemCachePool` you need to configure a `Filesystem` and its adapter. + +```php +use League\Flysystem\Adapter\Local; +use League\Flysystem\Filesystem; +use Cache\Adapter\Filesystem\FilesystemCachePool; + +$filesystemAdapter = new Local(__DIR__.'/'); +$filesystem = new Filesystem($filesystemAdapter); + +$pool = new FilesystemCachePool($filesystem); +``` + +You can change the folder the cache pool will write to through the `setFolder` setter: + +```php +$pool = new FilesystemCachePool($filesystem); +$pool->setFolder('path/to/cache'); +``` + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/filesystem-adapter/composer.json b/vendor/cache/filesystem-adapter/composer.json new file mode 100644 index 0000000..d2f794d --- /dev/null +++ b/vendor/cache/filesystem-adapter/composer.json @@ -0,0 +1,55 @@ +{ + "name": "cache/filesystem-adapter", + "description": "A PSR-6 cache implementation using filesystem. This implementation supports tags", + "license": "MIT", + "type": "library", + "keywords": [ + "cache", + "psr-6", + "filesystem", + "tag" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "homepage": "http://www.php-cache.com/en/latest/", + "require": { + "php": ">=7.4", + "cache/adapter-common": "^1.0", + "league/flysystem": "^1.0", + "psr/cache": "^1.0 || ^2.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "cache/integration-tests": "^0.17", + "phpunit/phpunit": "^7.5.20 || ^9.5.10" + }, + "provide": { + "psr/cache-implementation": "^1.0", + "psr/simple-cache-implementation": "^1.0" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/vendor/cache/tag-interop/.github/PULL_REQUEST_TEMPLATE.md b/vendor/cache/tag-interop/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4a339b4 --- /dev/null +++ b/vendor/cache/tag-interop/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +This is a READ ONLY repository. + +Please make your pull request to https://github.com/php-cache/cache + +Thank you for contributing. diff --git a/vendor/cache/tag-interop/.gitignore b/vendor/cache/tag-interop/.gitignore new file mode 100644 index 0000000..987e2a2 --- /dev/null +++ b/vendor/cache/tag-interop/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/vendor/cache/tag-interop/.travis.yml b/vendor/cache/tag-interop/.travis.yml new file mode 100644 index 0000000..45a1672 --- /dev/null +++ b/vendor/cache/tag-interop/.travis.yml @@ -0,0 +1,22 @@ +language: php +sudo: false + +matrix: + include: + - php: 7.1 + +cache: + directories: + - "$HOME/.composer/cache" + +install: + - composer update --prefer-dist --prefer-stable + +script: + - ./vendor/bin/phpunit --coverage-clover=coverage.xml + +after_success: + - pip install --user codecov && codecov + +notifications: + email: false diff --git a/vendor/cache/tag-interop/Changelog.md b/vendor/cache/tag-interop/Changelog.md new file mode 100644 index 0000000..974da3e --- /dev/null +++ b/vendor/cache/tag-interop/Changelog.md @@ -0,0 +1,18 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## 1.1.0 + +* Support PHP 8.1 +* Support for psr/cache v2 + +## 1.0.1 + +* Support PHP 8 + +## 1.0.0 + +* First release + + diff --git a/vendor/cache/tag-interop/LICENSE b/vendor/cache/tag-interop/LICENSE new file mode 100644 index 0000000..82f8fee --- /dev/null +++ b/vendor/cache/tag-interop/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aaron Scherer, Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/cache/tag-interop/README.md b/vendor/cache/tag-interop/README.md new file mode 100644 index 0000000..28511c9 --- /dev/null +++ b/vendor/cache/tag-interop/README.md @@ -0,0 +1,25 @@ +# Tag support for PSR-6 Cache +[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Latest Stable Version](https://poser.pugx.org/cache/tag-interop/v/stable)](https://packagist.org/packages/cache/tag-interop) +[![Total Downloads](https://poser.pugx.org/cache/tag-interop/downloads)](https://packagist.org/packages/cache/tag-interop) +[![Monthly Downloads](https://poser.pugx.org/cache/tag-interop/d/monthly.png)](https://packagist.org/packages/cache/tag-interop) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This repository holds two interfaces for tagging. These interfaces will make their +way into PHP Fig. Representatives from Symfony, PHP-cache and Drupal has worked +together to agree on these interfaces. + +### Install + +```bash +composer require cache/tag-interop +``` + +### Use + +Read the [documentation on usage](http://www.php-cache.com/). + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or +report any issues you find on the [issue tracker](http://issues.php-cache.com). diff --git a/vendor/cache/tag-interop/TaggableCacheItemInterface.php b/vendor/cache/tag-interop/TaggableCacheItemInterface.php new file mode 100644 index 0000000..5823b0b --- /dev/null +++ b/vendor/cache/tag-interop/TaggableCacheItemInterface.php @@ -0,0 +1,43 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\TagInterop; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * An item that supports tags. This interface is a soon-to-be-PSR. + * + * @author Tobias Nyholm + * @author Nicolas Grekas + */ +interface TaggableCacheItemInterface extends CacheItemInterface +{ + /** + * Get all existing tags. These are the tags the item has when the item is + * returned from the pool. + * + * @return array + */ + public function getPreviousTags(); + + /** + * Overwrite all tags with a new set of tags. + * + * @param string[] $tags An array of tags + * + * @throws InvalidArgumentException When a tag is not valid. + * + * @return TaggableCacheItemInterface + */ + public function setTags(array $tags); +} diff --git a/vendor/cache/tag-interop/TaggableCacheItemPoolInterface.php b/vendor/cache/tag-interop/TaggableCacheItemPoolInterface.php new file mode 100644 index 0000000..055bf4b --- /dev/null +++ b/vendor/cache/tag-interop/TaggableCacheItemPoolInterface.php @@ -0,0 +1,60 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\TagInterop; + +use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException; + +/** + * Interface for invalidating cached items using tags. This interface is a soon-to-be-PSR. + * + * @author Tobias Nyholm + * @author Nicolas Grekas + */ +interface TaggableCacheItemPoolInterface extends CacheItemPoolInterface +{ + /** + * Invalidates cached items using a tag. + * + * @param string $tag The tag to invalidate + * + * @throws InvalidArgumentException When $tags is not valid + * + * @return bool True on success + */ + public function invalidateTag($tag); + + /** + * Invalidates cached items using tags. + * + * @param string[] $tags An array of tags to invalidate + * + * @throws InvalidArgumentException When $tags is not valid + * + * @return bool True on success + */ + public function invalidateTags(array $tags); + + /** + * {@inheritdoc} + * + * @return TaggableCacheItemInterface + */ + public function getItem($key); + + /** + * {@inheritdoc} + * + * @return array|\Traversable|TaggableCacheItemInterface[] + */ + public function getItems(array $keys = []); +} diff --git a/vendor/cache/tag-interop/composer.json b/vendor/cache/tag-interop/composer.json new file mode 100644 index 0000000..03dacbe --- /dev/null +++ b/vendor/cache/tag-interop/composer.json @@ -0,0 +1,39 @@ +{ + "name": "cache/tag-interop", + "description": "Framework interoperable interfaces for tags", + "license": "MIT", + "type": "library", + "keywords": [ + "cache", + "psr6", + "tag", + "psr" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + }, + { + "name": "Nicolas Grekas ", + "email": "p@tchwork.com", + "homepage": "https://github.com/nicolas-grekas" + } + ], + "homepage": "https://www.php-cache.com/en/latest/", + "require": { + "php": "^5.5 || ^7.0 || ^8.0", + "psr/cache": "^1.0 || ^2.0" + }, + "autoload": { + "psr-4": { + "Cache\\TagInterop\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..afef3fa --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,572 @@ + + * 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 https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var ?string */ + private $vendorDir; + + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ + private $missingClasses = array(); + + /** @var ?string */ + private $apcuPrefix; + + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } + + /** + * @return string[] + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array[] + * @psalm-return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return string[] Array of classname => path + * @psalm-return array + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void + */ + 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 string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + 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 string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + 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 string[]|string $paths The PSR-0 base directories + * + * @return void + */ + 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 string[]|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + 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 + * + * @return void + */ + 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 + * + * @return void + */ + 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 + * + * @return void + */ + 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 + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + + return null; + } + + /** + * 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; + } + + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + 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. + * + * @param string $file + * @return void + * @private + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..41bc143 --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,352 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = require __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + $installed[] = self::$installed; + + return $installed; + } +} 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..350f034 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,28 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Callback' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackBody' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackParam' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackParameterToReference' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackReturnReference' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackReturnValue' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'DOMDocumentWrapper' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'DOMEvent' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'ICallbackNamed' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'phpQuery' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'phpQueryEvents' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'phpQueryObject' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', + 'phpQueryPlugins' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..7b238f2 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,18 @@ + $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', + 'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php', + 'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($baseDir . '/src'), + 'Tightenco\\Collect\\' => array($vendorDir . '/tightenco/collect/src/Collect'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), + 'QL\\' => array($vendorDir . '/jaeger/querylist/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', $vendorDir . '/psr/http-factory/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'), + 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), + 'Jaeger\\' => array($vendorDir . '/jaeger/g-http/src'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'Cache\\TagInterop\\' => array($vendorDir . '/cache/tag-interop'), + 'Cache\\Adapter\\Filesystem\\' => array($vendorDir . '/cache/filesystem-adapter'), + 'Cache\\Adapter\\Common\\' => array($vendorDir . '/cache/adapter-common'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..71de7ad --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,57 @@ +register(true); + + $includeFiles = \Composer\Autoload\ComposerStaticInit8583f236fb7cd3ae1a5996e2fc00e413::$files; + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire8583f236fb7cd3ae1a5996e2fc00e413($fileIdentifier, $file); + } + + return $loader; + } +} + +/** + * @param string $fileIdentifier + * @param string $file + * @return void + */ +function composerRequire8583f236fb7cd3ae1a5996e2fc00e413($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..ede788f --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,186 @@ + __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', + 'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php', + 'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'Y' => + array ( + 'Yuanjiajia\\Qldome\\' => 18, + ), + 'T' => + array ( + 'Tightenco\\Collect\\' => 18, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Component\\VarDumper\\' => 28, + ), + 'Q' => + array ( + 'QL\\' => 3, + ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + 'Psr\\Cache\\' => 10, + ), + 'L' => + array ( + 'League\\MimeTypeDetection\\' => 25, + 'League\\Flysystem\\' => 17, + ), + 'J' => + array ( + 'Jaeger\\' => 7, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + 'C' => + array ( + 'Cache\\TagInterop\\' => 17, + 'Cache\\Adapter\\Filesystem\\' => 25, + 'Cache\\Adapter\\Common\\' => 21, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Yuanjiajia\\Qldome\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + 'Tightenco\\Collect\\' => + array ( + 0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Component\\VarDumper\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-dumper', + ), + 'QL\\' => + array ( + 0 => __DIR__ . '/..' . '/jaeger/querylist/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', + 1 => __DIR__ . '/..' . '/psr/http-factory/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'League\\MimeTypeDetection\\' => + array ( + 0 => __DIR__ . '/..' . '/league/mime-type-detection/src', + ), + 'League\\Flysystem\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem/src', + ), + 'Jaeger\\' => + array ( + 0 => __DIR__ . '/..' . '/jaeger/g-http/src', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'Cache\\TagInterop\\' => + array ( + 0 => __DIR__ . '/..' . '/cache/tag-interop', + ), + 'Cache\\Adapter\\Filesystem\\' => + array ( + 0 => __DIR__ . '/..' . '/cache/filesystem-adapter', + ), + 'Cache\\Adapter\\Common\\' => + array ( + 0 => __DIR__ . '/..' . '/cache/adapter-common', + ), + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Callback' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackBody' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackParam' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackParameterToReference' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackReturnReference' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'CallbackReturnValue' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'DOMDocumentWrapper' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'DOMEvent' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'ICallbackNamed' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'phpQuery' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'phpQueryEvents' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'phpQueryObject' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + 'phpQueryPlugins' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit8583f236fb7cd3ae1a5996e2fc00e413::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit8583f236fb7cd3ae1a5996e2fc00e413::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit8583f236fb7cd3ae1a5996e2fc00e413::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..4e9da06 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,1629 @@ +{ + "packages": [ + { + "name": "cache/adapter-common", + "version": "1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/adapter-common.git", + "reference": "8788309be72aa7be69b88cdc0687549c74a7d479" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/adapter-common/zipball/8788309be72aa7be69b88cdc0687549c74a7d479", + "reference": "8788309be72aa7be69b88cdc0687549c74a7d479", + "shasum": "" + }, + "require": { + "cache/tag-interop": "^1.0", + "php": ">=7.4", + "psr/cache": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "cache/integration-tests": "^0.17", + "phpunit/phpunit": "^7.5.20 || ^9.5.10" + }, + "time": "2022-01-15T15:47:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Cache\\Adapter\\Common\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "description": "Common classes for PSR-6 adapters", + "homepage": "http://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "psr-6", + "tag" + ], + "support": { + "source": "https://github.com/php-cache/adapter-common/tree/1.3.0" + }, + "install-path": "../cache/adapter-common" + }, + { + "name": "cache/filesystem-adapter", + "version": "1.2.0", + "version_normalized": "1.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/filesystem-adapter.git", + "reference": "f1faaae40aaa696ef899cef6f6888aedb90b419b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/filesystem-adapter/zipball/f1faaae40aaa696ef899cef6f6888aedb90b419b", + "reference": "f1faaae40aaa696ef899cef6f6888aedb90b419b", + "shasum": "" + }, + "require": { + "cache/adapter-common": "^1.0", + "league/flysystem": "^1.0", + "php": ">=7.4", + "psr/cache": "^1.0 || ^2.0", + "psr/simple-cache": "^1.0" + }, + "provide": { + "psr/cache-implementation": "^1.0", + "psr/simple-cache-implementation": "^1.0" + }, + "require-dev": { + "cache/integration-tests": "^0.17", + "phpunit/phpunit": "^7.5.20 || ^9.5.10" + }, + "time": "2022-01-15T15:47:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Cache\\Adapter\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "description": "A PSR-6 cache implementation using filesystem. This implementation supports tags", + "homepage": "http://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "filesystem", + "psr-6", + "tag" + ], + "support": { + "source": "https://github.com/php-cache/filesystem-adapter/tree/1.2.0" + }, + "install-path": "../cache/filesystem-adapter" + }, + { + "name": "cache/tag-interop", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/tag-interop.git", + "reference": "b062b1d735357da50edf8387f7a8696f3027d328" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/tag-interop/zipball/b062b1d735357da50edf8387f7a8696f3027d328", + "reference": "b062b1d735357da50edf8387f7a8696f3027d328", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0 || ^8.0", + "psr/cache": "^1.0 || ^2.0" + }, + "time": "2021-12-31T10:03:23+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Cache\\TagInterop\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com", + "homepage": "https://github.com/nicolas-grekas" + } + ], + "description": "Framework interoperable interfaces for tags", + "homepage": "https://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "psr", + "psr6", + "tag" + ], + "support": { + "issues": "https://github.com/php-cache/tag-interop/issues", + "source": "https://github.com/php-cache/tag-interop/tree/1.1.0" + }, + "install-path": "../cache/tag-interop" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.5.0", + "version_normalized": "7.5.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "time": "2022-08-28T15:39:27+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/guzzle" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.2", + "version_normalized": "1.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "b94b2807d85443f9719887892882d0329d1e2598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "time": "2022-08-28T14:55:35+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/promises" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.3", + "version_normalized": "2.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "67c26b443f348a51926030c83481b85718457d3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", + "reference": "67c26b443f348a51926030c83481b85718457d3d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "time": "2022-10-26T14:07:24+00:00", + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/psr7" + }, + { + "name": "jaeger/g-http", + "version": "V1.7.2", + "version_normalized": "1.7.2.0", + "source": { + "type": "git", + "url": "https://github.com/jae-jae/GHttp.git", + "reference": "82585ddd5e2c6651e37ab1d8166efcdbb6b293d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jae-jae/GHttp/zipball/82585ddd5e2c6651e37ab1d8166efcdbb6b293d4", + "reference": "82585ddd5e2c6651e37ab1d8166efcdbb6b293d4", + "shasum": "" + }, + "require": { + "cache/filesystem-adapter": "^1", + "guzzlehttp/guzzle": "^6.0 | ^7.0" + }, + "time": "2021-08-08T04:59:44+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Jaeger\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaeger", + "email": "JaegerCode@gmail.com" + } + ], + "description": "Simple Http client base on GuzzleHttp", + "support": { + "issues": "https://github.com/jae-jae/GHttp/issues", + "source": "https://github.com/jae-jae/GHttp/tree/V1.7.2" + }, + "install-path": "../jaeger/g-http" + }, + { + "name": "jaeger/phpquery-single", + "version": "1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/jae-jae/phpQuery-single.git", + "reference": "39a650ade692a6b480c22220dce0c198d6a946fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jae-jae/phpQuery-single/zipball/39a650ade692a6b480c22220dce0c198d6a946fb", + "reference": "39a650ade692a6b480c22220dce0c198d6a946fb", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2022-03-26T15:01:16+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "phpQuery.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobiasz Cudnik", + "email": "tobiasz.cudnik@gmail.com", + "homepage": "https://github.com/TobiaszCudnik", + "role": "Developer" + }, + { + "name": "Jaeger", + "role": "Packager" + } + ], + "description": "phpQuery单文件版本,是Querylist的依赖(http://querylist.cc/),phpQuery项目主页:http://code.google.com/p/phpquery/", + "homepage": "http://code.google.com/p/phpquery/", + "support": { + "issues": "https://github.com/jae-jae/phpQuery-single/issues", + "source": "https://github.com/jae-jae/phpQuery-single/tree/1.1.1" + }, + "install-path": "../jaeger/phpquery-single" + }, + { + "name": "jaeger/querylist", + "version": "V4.2.8", + "version_normalized": "4.2.8.0", + "source": { + "type": "git", + "url": "https://github.com/jae-jae/QueryList.git", + "reference": "39dc0ca9c668bec7a793e20472ccd7d26ef89ea4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jae-jae/QueryList/zipball/39dc0ca9c668bec7a793e20472ccd7d26ef89ea4", + "reference": "39dc0ca9c668bec7a793e20472ccd7d26ef89ea4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "jaeger/g-http": "^1.1", + "jaeger/phpquery-single": "^1", + "php": ">=7.1", + "tightenco/collect": ">5.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5", + "symfony/var-dumper": "^3.3" + }, + "time": "2021-07-05T06:07:58+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "QL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaeger", + "email": "JaegerCode@gmail.com" + } + ], + "description": "Simple, elegant, extensible PHP Web Scraper (crawler/spider),Use the css3 dom selector,Based on phpQuery! 简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。", + "homepage": "http://querylist.cc", + "keywords": [ + "QueryList", + "phpQuery", + "spider" + ], + "support": { + "issues": "https://github.com/jae-jae/QueryList/issues", + "source": "https://github.com/jae-jae/QueryList/tree/V4.2.8" + }, + "funding": [ + { + "url": "https://opencollective.com/querylist", + "type": "open_collective" + } + ], + "install-path": "../jaeger/querylist" + }, + { + "name": "league/flysystem", + "version": "1.1.10", + "version_normalized": "1.1.10.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "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": "2022-10-04T09:16:37+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" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.10" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "install-path": "../league/flysystem" + }, + { + "name": "league/mime-type-detection", + "version": "1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "time": "2022-04-17T13:12:02+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "install-path": "../league/mime-type-detection" + }, + { + "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" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "install-path": "../psr/cache" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "time": "2020-06-29T06:28:15+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "install-path": "../psr/http-client" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "time": "2019-04-30T12:38:16+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 interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "install-path": "../psr/http-factory" + }, + { + "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" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "install-path": "../psr/http-message" + }, + { + "name": "psr/log", + "version": "1.1.4", + "version_normalized": "1.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2021-05-03T11:20:27+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": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "install-path": "../psr/log" + }, + { + "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" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "install-path": "../psr/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.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "install-path": "../ralouphie/getallheaders" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-01-02T09:53:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/var-dumper", + "version": "v5.4.14", + "version_normalized": "5.4.14.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "6894d06145fefebd9a4c7272baa026a1c394a430" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6894d06145fefebd9a4c7272baa026a1c394a430", + "reference": "6894d06145fefebd9a4c7272baa026a1c394a430", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "time": "2022-10-07T08:01:20+00:00", + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v5.4.14" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/var-dumper" + }, + { + "name": "tightenco/collect", + "version": "v8.83.25", + "version_normalized": "8.83.25.0", + "source": { + "type": "git", + "url": "https://github.com/tighten/collect.git", + "reference": "7d2a6fc5e97c5f7209a780bea98f35042c1fd0ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tighten/collect/zipball/7d2a6fc5e97c5f7209a780bea98f35042c1fd0ea", + "reference": "7d2a6fc5e97c5f7209a780bea98f35042c1fd0ea", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0", + "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "nesbot/carbon": "^2.23.0", + "phpunit/phpunit": "^8.3" + }, + "time": "2022-08-22T17:55:07+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/Collect/Support/helpers.php", + "src/Collect/Support/alias.php" + ], + "psr-4": { + "Tightenco\\Collect\\": "src/Collect" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "Collect - Illuminate Collections as a separate package.", + "keywords": [ + "collection", + "laravel" + ], + "support": { + "issues": "https://github.com/tighten/collect/issues", + "source": "https://github.com/tighten/collect/tree/v8.83.25" + }, + "install-path": "../tightenco/collect" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..579d6df --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,260 @@ + array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => NULL, + 'name' => 'yuanjiajia/qldome', + 'dev' => true, + ), + 'versions' => array( + 'cache/adapter-common' => array( + 'pretty_version' => '1.3.0', + 'version' => '1.3.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../cache/adapter-common', + 'aliases' => array(), + 'reference' => '8788309be72aa7be69b88cdc0687549c74a7d479', + 'dev_requirement' => false, + ), + 'cache/filesystem-adapter' => array( + 'pretty_version' => '1.2.0', + 'version' => '1.2.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../cache/filesystem-adapter', + 'aliases' => array(), + 'reference' => 'f1faaae40aaa696ef899cef6f6888aedb90b419b', + 'dev_requirement' => false, + ), + 'cache/tag-interop' => array( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../cache/tag-interop', + 'aliases' => array(), + 'reference' => 'b062b1d735357da50edf8387f7a8696f3027d328', + 'dev_requirement' => false, + ), + 'guzzlehttp/guzzle' => array( + 'pretty_version' => '7.5.0', + 'version' => '7.5.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', + 'aliases' => array(), + 'reference' => 'b50a2a1251152e43f6a37f0fa053e730a67d25ba', + 'dev_requirement' => false, + ), + 'guzzlehttp/promises' => array( + 'pretty_version' => '1.5.2', + 'version' => '1.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/promises', + 'aliases' => array(), + 'reference' => 'b94b2807d85443f9719887892882d0329d1e2598', + 'dev_requirement' => false, + ), + 'guzzlehttp/psr7' => array( + 'pretty_version' => '2.4.3', + 'version' => '2.4.3.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/psr7', + 'aliases' => array(), + 'reference' => '67c26b443f348a51926030c83481b85718457d3d', + 'dev_requirement' => false, + ), + 'jaeger/g-http' => array( + 'pretty_version' => 'V1.7.2', + 'version' => '1.7.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../jaeger/g-http', + 'aliases' => array(), + 'reference' => '82585ddd5e2c6651e37ab1d8166efcdbb6b293d4', + 'dev_requirement' => false, + ), + 'jaeger/phpquery-single' => array( + 'pretty_version' => '1.1.1', + 'version' => '1.1.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../jaeger/phpquery-single', + 'aliases' => array(), + 'reference' => '39a650ade692a6b480c22220dce0c198d6a946fb', + 'dev_requirement' => false, + ), + 'jaeger/querylist' => array( + 'pretty_version' => 'V4.2.8', + 'version' => '4.2.8.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../jaeger/querylist', + 'aliases' => array(), + 'reference' => '39dc0ca9c668bec7a793e20472ccd7d26ef89ea4', + 'dev_requirement' => false, + ), + 'league/flysystem' => array( + 'pretty_version' => '1.1.10', + 'version' => '1.1.10.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/flysystem', + 'aliases' => array(), + 'reference' => '3239285c825c152bcc315fe0e87d6b55f5972ed1', + 'dev_requirement' => false, + ), + 'league/mime-type-detection' => array( + 'pretty_version' => '1.11.0', + 'version' => '1.11.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/mime-type-detection', + 'aliases' => array(), + 'reference' => 'ff6248ea87a9f116e78edd6002e39e5128a0d4dd', + 'dev_requirement' => false, + ), + 'psr/cache' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/cache', + 'aliases' => array(), + 'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8', + 'dev_requirement' => false, + ), + 'psr/cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '^1.0', + ), + ), + 'psr/http-client' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-client', + 'aliases' => array(), + 'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621', + 'dev_requirement' => false, + ), + 'psr/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-factory' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-factory', + 'aliases' => array(), + 'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be', + 'dev_requirement' => false, + ), + 'psr/http-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-message' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', + 'dev_requirement' => false, + ), + 'psr/http-message-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/log' => array( + 'pretty_version' => '1.1.4', + 'version' => '1.1.4.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', + 'dev_requirement' => false, + ), + 'psr/simple-cache' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/simple-cache', + 'aliases' => array(), + 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', + 'dev_requirement' => false, + ), + 'psr/simple-cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '^1.0', + ), + ), + 'ralouphie/getallheaders' => array( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ralouphie/getallheaders', + 'aliases' => array(), + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66', + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'reference' => '8ad114f6b39e2c98a8b0e3bd907732c207c2b534', + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936', + 'dev_requirement' => false, + ), + 'symfony/var-dumper' => array( + 'pretty_version' => 'v5.4.14', + 'version' => '5.4.14.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/var-dumper', + 'aliases' => array(), + 'reference' => '6894d06145fefebd9a4c7272baa026a1c394a430', + 'dev_requirement' => false, + ), + 'tightenco/collect' => array( + 'pretty_version' => 'v8.83.25', + 'version' => '8.83.25.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../tightenco/collect', + 'aliases' => array(), + 'reference' => '7d2a6fc5e97c5f7209a780bea98f35042c1fd0ea', + 'dev_requirement' => false, + ), + 'yuanjiajia/qldome' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'reference' => NULL, + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..580fa96 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70400)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/vendor/guzzlehttp/guzzle/CHANGELOG.md b/vendor/guzzlehttp/guzzle/CHANGELOG.md new file mode 100644 index 0000000..12949ba --- /dev/null +++ b/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -0,0 +1,1519 @@ +# Change Log + +Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version. + +## 7.5.0 - 2022-08-28 + +### Added + +- Support PHP 8.2 +- Add request to delay closure params + +## 7.4.5 - 2022-06-20 + +* Fix change in port should be considered a change in origin +* Fix `CURLOPT_HTTPAUTH` option not cleared on change of origin + +## 7.4.4 - 2022-06-09 + +* Fix failure to strip Authorization header on HTTP downgrade +* Fix failure to strip the Cookie header on change in host or HTTP downgrade + +## 7.4.3 - 2022-05-25 + +* Fix cross-domain cookie leakage + +## 7.4.2 - 2022-03-20 + +### Fixed + +- Remove curl auth on cross-domain redirects to align with the Authorization HTTP header +- Reject non-HTTP schemes in StreamHandler +- Set a default ssl.peer_name context in StreamHandler to allow `force_ip_resolve` + +## 7.4.1 - 2021-12-06 + +### Changed + +- Replaced implicit URI to string coercion [#2946](https://github.com/guzzle/guzzle/pull/2946) +- Allow `symfony/deprecation-contracts` version 3 [#2961](https://github.com/guzzle/guzzle/pull/2961) + +### Fixed + +- Only close curl handle if it's done [#2950](https://github.com/guzzle/guzzle/pull/2950) + +## 7.4.0 - 2021-10-18 + +### Added + +- Support PHP 8.1 [#2929](https://github.com/guzzle/guzzle/pull/2929), [#2939](https://github.com/guzzle/guzzle/pull/2939) +- Support `psr/log` version 2 and 3 [#2943](https://github.com/guzzle/guzzle/pull/2943) + +### Fixed + +- Make sure we always call `restore_error_handler()` [#2915](https://github.com/guzzle/guzzle/pull/2915) +- Fix progress parameter type compatibility between the cURL and stream handlers [#2936](https://github.com/guzzle/guzzle/pull/2936) +- Throw `InvalidArgumentException` when an incorrect `headers` array is provided [#2916](https://github.com/guzzle/guzzle/pull/2916), [#2942](https://github.com/guzzle/guzzle/pull/2942) + +### Changed + +- Be more strict with types [#2914](https://github.com/guzzle/guzzle/pull/2914), [#2917](https://github.com/guzzle/guzzle/pull/2917), [#2919](https://github.com/guzzle/guzzle/pull/2919), [#2945](https://github.com/guzzle/guzzle/pull/2945) + +## 7.3.0 - 2021-03-23 + +### Added + +- Support for DER and P12 certificates [#2413](https://github.com/guzzle/guzzle/pull/2413) +- Support the cURL (http://) scheme for StreamHandler proxies [#2850](https://github.com/guzzle/guzzle/pull/2850) +- Support for `guzzlehttp/psr7:^2.0` [#2878](https://github.com/guzzle/guzzle/pull/2878) + +### Fixed + +- Handle exceptions on invalid header consistently between PHP versions and handlers [#2872](https://github.com/guzzle/guzzle/pull/2872) + +## 7.2.0 - 2020-10-10 + +### Added + +- Support for PHP 8 [#2712](https://github.com/guzzle/guzzle/pull/2712), [#2715](https://github.com/guzzle/guzzle/pull/2715), [#2789](https://github.com/guzzle/guzzle/pull/2789) +- Support passing a body summarizer to the http errors middleware [#2795](https://github.com/guzzle/guzzle/pull/2795) + +### Fixed + +- Handle exceptions during response creation [#2591](https://github.com/guzzle/guzzle/pull/2591) +- Fix CURLOPT_ENCODING not to be overwritten [#2595](https://github.com/guzzle/guzzle/pull/2595) +- Make sure the Request always has a body object [#2804](https://github.com/guzzle/guzzle/pull/2804) + +### Changed + +- The `TooManyRedirectsException` has a response [#2660](https://github.com/guzzle/guzzle/pull/2660) +- Avoid "functions" from dependencies [#2712](https://github.com/guzzle/guzzle/pull/2712) + +### Deprecated + +- Using environment variable GUZZLE_CURL_SELECT_TIMEOUT [#2786](https://github.com/guzzle/guzzle/pull/2786) + +## 7.1.1 - 2020-09-30 + +### Fixed + +- Incorrect EOF detection for response body streams on Windows. + +### Changed + +- We dont connect curl `sink` on HEAD requests. +- Removed some PHP 5 workarounds + +## 7.1.0 - 2020-09-22 + +### Added + +- `GuzzleHttp\MessageFormatterInterface` + +### Fixed + +- Fixed issue that caused cookies with no value not to be stored. +- On redirects, we allow all safe methods like GET, HEAD and OPTIONS. +- Fixed logging on empty responses. +- Make sure MessageFormatter::format returns string + +### Deprecated + +- All functions in `GuzzleHttp` has been deprecated. Use static methods on `Utils` instead. +- `ClientInterface::getConfig()` +- `Client::getConfig()` +- `Client::__call()` +- `Utils::defaultCaBundle()` +- `CurlFactory::LOW_CURL_VERSION_NUMBER` + +## 7.0.1 - 2020-06-27 + +* Fix multiply defined functions fatal error [#2699](https://github.com/guzzle/guzzle/pull/2699) + +## 7.0.0 - 2020-06-27 + +No changes since 7.0.0-rc1. + +## 7.0.0-rc1 - 2020-06-15 + +### Changed + +* Use error level for logging errors in Middleware [#2629](https://github.com/guzzle/guzzle/pull/2629) +* Disabled IDN support by default and require ext-intl to use it [#2675](https://github.com/guzzle/guzzle/pull/2675) + +## 7.0.0-beta2 - 2020-05-25 + +### Added + +* Using `Utils` class instead of functions in the `GuzzleHttp` namespace. [#2546](https://github.com/guzzle/guzzle/pull/2546) +* `ClientInterface::MAJOR_VERSION` [#2583](https://github.com/guzzle/guzzle/pull/2583) + +### Changed + +* Avoid the `getenv` function when unsafe [#2531](https://github.com/guzzle/guzzle/pull/2531) +* Added real client methods [#2529](https://github.com/guzzle/guzzle/pull/2529) +* Avoid functions due to global install conflicts [#2546](https://github.com/guzzle/guzzle/pull/2546) +* Use Symfony intl-idn polyfill [#2550](https://github.com/guzzle/guzzle/pull/2550) +* Adding methods for HTTP verbs like `Client::get()`, `Client::head()`, `Client::patch()` etc [#2529](https://github.com/guzzle/guzzle/pull/2529) +* `ConnectException` extends `TransferException` [#2541](https://github.com/guzzle/guzzle/pull/2541) +* Updated the default User Agent to "GuzzleHttp/7" [#2654](https://github.com/guzzle/guzzle/pull/2654) + +### Fixed + +* Various intl icu issues [#2626](https://github.com/guzzle/guzzle/pull/2626) + +### Removed + +* Pool option `pool_size` [#2528](https://github.com/guzzle/guzzle/pull/2528) + +## 7.0.0-beta1 - 2019-12-30 + +The diff might look very big but 95% of Guzzle users will be able to upgrade without modification. +Please see [the upgrade document](UPGRADING.md) that describes all BC breaking changes. + +### Added + +* Implement PSR-18 and dropped PHP 5 support [#2421](https://github.com/guzzle/guzzle/pull/2421) [#2474](https://github.com/guzzle/guzzle/pull/2474) +* PHP 7 types [#2442](https://github.com/guzzle/guzzle/pull/2442) [#2449](https://github.com/guzzle/guzzle/pull/2449) [#2466](https://github.com/guzzle/guzzle/pull/2466) [#2497](https://github.com/guzzle/guzzle/pull/2497) [#2499](https://github.com/guzzle/guzzle/pull/2499) +* IDN support for redirects [2424](https://github.com/guzzle/guzzle/pull/2424) + +### Changed + +* Dont allow passing null as third argument to `BadResponseException::__construct()` [#2427](https://github.com/guzzle/guzzle/pull/2427) +* Use SAPI constant instead of method call [#2450](https://github.com/guzzle/guzzle/pull/2450) +* Use native function invocation [#2444](https://github.com/guzzle/guzzle/pull/2444) +* Better defaults for PHP installations with old ICU lib [2454](https://github.com/guzzle/guzzle/pull/2454) +* Added visibility to all constants [#2462](https://github.com/guzzle/guzzle/pull/2462) +* Dont allow passing `null` as URI to `Client::request()` and `Client::requestAsync()` [#2461](https://github.com/guzzle/guzzle/pull/2461) +* Widen the exception argument to throwable [#2495](https://github.com/guzzle/guzzle/pull/2495) + +### Fixed + +* Logging when Promise rejected with a string [#2311](https://github.com/guzzle/guzzle/pull/2311) + +### Removed + +* Class `SeekException` [#2162](https://github.com/guzzle/guzzle/pull/2162) +* `RequestException::getResponseBodySummary()` [#2425](https://github.com/guzzle/guzzle/pull/2425) +* `CookieJar::getCookieValue()` [#2433](https://github.com/guzzle/guzzle/pull/2433) +* `uri_template()` and `UriTemplate` [#2440](https://github.com/guzzle/guzzle/pull/2440) +* Request options `save_to` and `exceptions` [#2464](https://github.com/guzzle/guzzle/pull/2464) + +## 6.5.2 - 2019-12-23 + +* idn_to_ascii() fix for old PHP versions [#2489](https://github.com/guzzle/guzzle/pull/2489) + +## 6.5.1 - 2019-12-21 + +* Better defaults for PHP installations with old ICU lib [#2454](https://github.com/guzzle/guzzle/pull/2454) +* IDN support for redirects [#2424](https://github.com/guzzle/guzzle/pull/2424) + +## 6.5.0 - 2019-12-07 + +* Improvement: Added support for reset internal queue in MockHandler. [#2143](https://github.com/guzzle/guzzle/pull/2143) +* Improvement: Added support to pass arbitrary options to `curl_multi_init`. [#2287](https://github.com/guzzle/guzzle/pull/2287) +* Fix: Gracefully handle passing `null` to the `header` option. [#2132](https://github.com/guzzle/guzzle/pull/2132) +* Fix: `RetryMiddleware` did not do exponential delay between 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 https://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: https://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 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 https://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/LICENSE b/vendor/guzzlehttp/guzzle/LICENSE new file mode 100644 index 0000000..fd2375d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,27 @@ +The MIT License (MIT) + +Copyright (c) 2011 Michael Dowling +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/guzzle/README.md b/vendor/guzzlehttp/guzzle/README.md new file mode 100644 index 0000000..f287fa9 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/README.md @@ -0,0 +1,94 @@ +![Guzzle](.github/logo.png?raw=true) + +# Guzzle, PHP HTTP client + +[![Latest Version](https://img.shields.io/github/release/guzzle/guzzle.svg?style=flat-square)](https://github.com/guzzle/guzzle/releases) +[![Build Status](https://img.shields.io/github/workflow/status/guzzle/guzzle/CI?label=ci%20build&style=flat-square)](https://github.com/guzzle/guzzle/actions?query=workflow%3ACI) +[![Total Downloads](https://img.shields.io/packagist/dt/guzzlehttp/guzzle.svg?style=flat-square)](https://packagist.org/packages/guzzlehttp/guzzle) + +Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Simple interface for building query strings, POST requests, streaming large + uploads, streaming large downloads, using HTTP cookies, uploading JSON data, + etc... +- Can send both synchronous and asynchronous requests using the same interface. +- Uses PSR-7 interfaces for requests, responses, and streams. This allows you + to utilize other PSR-7 compatible libraries with Guzzle. +- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients. +- 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 + +We use GitHub issues only to discuss bugs and new features. For support please refer to: + +- [Documentation](https://docs.guzzlephp.org) +- [Stack Overflow](https://stackoverflow.com/questions/tagged/guzzle) +- [#guzzle](https://app.slack.com/client/T0D2S9JCT/CE6UAAKL4) channel on [PHP-HTTP Slack](https://slack.httplug.io/) +- [Gitter](https://gitter.im/guzzle/guzzle) + + +## Installing Guzzle + +The recommended way to install Guzzle is through +[Composer](https://getcomposer.org/). + +```bash +composer require guzzlehttp/guzzle +``` + + +## Version Guidance + +| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version | +|---------|----------------|---------------------|--------------|---------------------|---------------------|-------|--------------| +| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 | +| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 | +| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 | +| 6.x | Security fixes | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 | +| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.2 | + +[guzzle-3-repo]: https://github.com/guzzle/guzzle3 +[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x +[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3 +[guzzle-6-repo]: https://github.com/guzzle/guzzle/tree/6.5 +[guzzle-7-repo]: https://github.com/guzzle/guzzle +[guzzle-3-docs]: https://guzzle3.readthedocs.io/ +[guzzle-5-docs]: https://docs.guzzlephp.org/en/5.3/ +[guzzle-6-docs]: https://docs.guzzlephp.org/en/6.5/ +[guzzle-7-docs]: https://docs.guzzlephp.org/en/latest/ + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/guzzle/security/policy) for more information. + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-guzzle?utm_source=packagist-guzzlehttp-guzzle&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/guzzle/UPGRADING.md b/vendor/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 0000000..45417a7 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1253 @@ +Guzzle Upgrade Guide +==================== + +6.0 to 7.0 +---------- + +In order to take advantage of the new features of PHP, Guzzle dropped the support +of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return +types for functions and methods have been added wherever possible. + +Please make sure: +- You are calling a function or a method with the correct type. +- If you extend a class of Guzzle; update all signatures on methods you override. + +#### Other backwards compatibility breaking changes + +- Class `GuzzleHttp\UriTemplate` is removed. +- Class `GuzzleHttp\Exception\SeekException` is removed. +- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`, + `GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty + Response as argument. +- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException` + instead of `GuzzleHttp\Exception\RequestException`. +- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed. +- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed. +- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead. +- Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed. + Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative. +- Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed. +- Request option `exception` is removed. Please use `http_errors`. +- Request option `save_to` is removed. Please use `sink`. +- Pool option `pool_size` is removed. Please use `concurrency`. +- We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility. +- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation. +- The `log` middleware will log the errors with level `error` instead of `notice` +- Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher). + +#### Native functions calls + +All internal native functions calls of Guzzle are now prefixed with a slash. This +change makes it impossible for method overloading by other libraries or applications. +Example: + +```php +// Before: +curl_version(); + +// After: +\curl_version(); +``` + +For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master). + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](https://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](https://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 https://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..f369ce6 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,105 @@ +{ + "name": "guzzlehttp/guzzle", + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "framework", + "http", + "rest", + "web service", + "curl", + "client", + "HTTP client", + "PSR-7", + "PSR-18" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "bamarni/composer-bin-plugin": "^1.8.1", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "7.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/BodySummarizer.php b/vendor/guzzlehttp/guzzle/src/BodySummarizer.php new file mode 100644 index 0000000..6eca94e --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/BodySummarizer.php @@ -0,0 +1,28 @@ +truncateAt = $truncateAt; + } + + /** + * Returns a summarized message body. + */ + public function summarize(MessageInterface $message): ?string + { + return $this->truncateAt === null + ? \GuzzleHttp\Psr7\Message::bodySummary($message) + : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php b/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php new file mode 100644 index 0000000..3e02e03 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/BodySummarizerInterface.php @@ -0,0 +1,13 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. + * If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + if (!isset($config['handler'])) { + $config['handler'] = HandlerStack::create(); + } elseif (!\is_callable($config['handler'])) { + throw new InvalidArgumentException('handler must be a callable'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']); + } + + $this->configureDefaults($config); + } + + /** + * @param string $method + * @param array $args + * + * @return PromiseInterface|ResponseInterface + * + * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0. + */ + 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 = $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. + */ + public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface + { + // 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. + * + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); + } + + /** + * The HttpClient PSR (PSR-18) specify this method. + * + * @inheritDoc + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + $options[RequestOptions::ALLOW_REDIRECTS] = false; + $options[RequestOptions::HTTP_ERRORS] = false; + + 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. + */ + public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface + { + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = $options['headers'] ?? []; + $body = $options['body'] ?? null; + $version = $options['version'] ?? '1.1'; + // Merge the URI into the base URI. + $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options); + if (\is_array($body)) { + throw $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. + * + * @throws GuzzleException + */ + public function request(string $method, $uri = '', array $options = []): ResponseInterface + { + $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 + * + * @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0. + */ + public function getConfig(?string $option = null) + { + return $option === null + ? $this->config + : ($this->config[$option] ?? null); + } + + private function buildUri(UriInterface $uri, array $config): UriInterface + { + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri); + } + + if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) { + $idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + */ + private function configureDefaults(array $config): void + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => false, + ]; + + // 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 === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) { + $defaults['proxy']['http'] = $proxy; + } + + if ($proxy = Utils::getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = Utils::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' => Utils::defaultUserAgent()]; + } 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'] = Utils::defaultUserAgent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + */ + private function prepareDefaults(array $options): array + { + $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. + */ + private function transfer(RequestInterface $request, array $options): PromiseInterface + { + $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ + $handler = $options['handler']; + + try { + return P\Create::promiseFor($handler($request, $options)); + } catch (\Exception $e) { + return P\Create::rejectionFor($e); + } + } + + /** + * Applies the array of request options to a request. + */ + private function applyOptions(RequestInterface $request, array &$options): RequestInterface + { + $modify = [ + 'set_headers' => [], + ]; + + if (isset($options['headers'])) { + if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) { + throw new InvalidArgumentException('The headers array must have header name as keys.'); + } + $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\Utils::caselessRemove(['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'] = Utils::jsonEncode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['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\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = $options['decode_content']; + } + + if (isset($options['body'])) { + if (\is_array($options['body'])) { + throw $this->invalidBody(); + } + $modify['body'] = Psr7\Utils::streamFor($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\Utils::caselessRemove(['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, '', '&', \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\Utils::modifyRequest($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\Utils::caselessRemove(['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\Utils::modifyRequest($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + /** + * Return an InvalidArgumentException with pre-set message. + */ + private function invalidBody(): InvalidArgumentException + { + return new InvalidArgumentException('Passing in the "body" request ' + . 'option as an array to send a request is not supported. ' + . '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..6aaee61 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,84 @@ +request('GET', $uri, $options); + } + + /** + * Create and send an HTTP HEAD 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function head($uri, array $options = []): ResponseInterface + { + return $this->request('HEAD', $uri, $options); + } + + /** + * Create and send an HTTP PUT 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function put($uri, array $options = []): ResponseInterface + { + return $this->request('PUT', $uri, $options); + } + + /** + * Create and send an HTTP POST 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function post($uri, array $options = []): ResponseInterface + { + return $this->request('POST', $uri, $options); + } + + /** + * Create and send an HTTP PATCH 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function patch($uri, array $options = []): ResponseInterface + { + return $this->request('PATCH', $uri, $options); + } + + /** + * Create and send an HTTP DELETE 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function delete($uri, array $options = []): ResponseInterface + { + return $this->request('DELETE', $uri, $options); + } + + /** + * 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. + */ + abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface; + + /** + * Create and send an asynchronous HTTP GET 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function getAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('GET', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP HEAD 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function headAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('HEAD', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PUT 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function putAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PUT', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP POST 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function postAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('POST', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PATCH 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function patchAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PATCH', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP DELETE 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|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function deleteAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('DELETE', $uri, $options); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 0000000..9985a98 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,317 @@ +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 + */ + public static function fromArray(array $cookies, string $domain): self + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * 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 + */ + public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool + { + 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(string $name): ?SetCookie + { + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return \array_map(static function (SetCookie $cookie): array { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + /** + * @inheritDoc + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($domain): bool { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain): bool { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + /** + * @inheritDoc + */ + public function clearSessionCookies(): void + { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie): bool { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + /** + * @inheritDoc + */ + public function setCookie(SetCookie $cookie): bool + { + // 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); + } + $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(): int + { + return \count($this->cookies); + } + + /** + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator(\array_values($this->cookies)); + } + + public function extractCookies(RequestInterface $request, ResponseInterface $response): void + { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== \strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + if (!$sc->matchesDomain($request->getUri()->getHost())) { + continue; + } + // Note: At this point `$sc->getDomain()` being a public suffix should + // be rejected, but we don't want to pull in the full PSL dependency. + $this->setCookie($sc); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + */ + private function getCookiePathFromRequest(RequestInterface $request): string + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== \strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + $lastSlashPos = \strrpos($uriPath, '/'); + if (0 === $lastSlashPos || false === $lastSlashPos) { + return '/'; + } + + return \substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request): RequestInterface + { + $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. + */ + private function removeCookieIfEmpty(SetCookie $cookie): void + { + $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..7df374b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,79 @@ + + */ +interface CookieJarInterface extends \Countable, \IteratorAggregate +{ + /** + * Create a request with added cookie headers. + * + * If no matching cookies are found in the cookie jar, then no Cookie + * header is added to the request and the same request is returned. + * + * @param RequestInterface $request Request object to modify. + * + * @return RequestInterface returns the modified request. + */ + public function withCookieHeader(RequestInterface $request): RequestInterface; + + /** + * Extract cookies from an HTTP response and store them in the CookieJar. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + */ + public function extractCookies(RequestInterface $request, ResponseInterface $response): void; + + /** + * Sets a cookie in the cookie jar. + * + * @param SetCookie $cookie Cookie to set. + * + * @return bool Returns true on success or false on failure + */ + public function setCookie(SetCookie $cookie): bool; + + /** + * Remove cookies currently held in the cookie jar. + * + * Invoking this method without arguments will empty the whole cookie jar. + * If given a $domain argument only cookies belonging to that domain will + * be removed. If given a $domain and $path argument, cookies belonging to + * the specified path within that domain are removed. If given all three + * arguments, then the cookie with the specified name, path and domain is + * removed. + * + * @param string|null $domain Clears cookies matching a domain + * @param string|null $path Clears cookies matching a domain and path + * @param string|null $name Clears cookies matching a domain, path, and name + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void; + + /** + * Discard all sessions cookies. + * + * Removes cookies that don't have an expire field or a have a discard + * field set to true. To be called when the user agent shuts down according + * to RFC 2965. + */ + public function clearSessionCookies(): void; + + /** + * Converts the cookie jar to an array. + */ + public function toArray(): array; +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php new file mode 100644 index 0000000..290236d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php @@ -0,0 +1,101 @@ +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(string $filename): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = Utils::jsonEncode($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(string $filename): void + { + $json = \file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } + if ($json === '') { + return; + } + + $data = Utils::jsonDecode($json, true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\is_scalar($data) && !empty($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..5d51ca9 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,77 @@ +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(): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $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(): void + { + 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..a613c77 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,446 @@ + 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 + */ + public static function fromString(string $cookie): self + { + // 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 (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) { + 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 (!isset($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 = []) + { + /** @var array|null $replaced will be null in case of replace error */ + $replaced = \array_replace(self::$defaults, $data); + if ($replaced === null) { + throw new \InvalidArgumentException('Unable to replace the default values for the Cookie.'); + } + + $this->data = $replaced; + // 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 (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) { + $this->setExpires($expires); + } + } + + 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(): array + { + 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): void + { + if (!is_string($name)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Name'] = (string) $name; + } + + /** + * Get the cookie value. + * + * @return string|null + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value. + * + * @param string $value Cookie value + */ + public function setValue($value): void + { + if (!is_string($value)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Value'] = (string) $value; + } + + /** + * Get the domain. + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie. + * + * @param string|null $domain + */ + public function setDomain($domain): void + { + if (!is_string($domain) && null !== $domain) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Domain'] = null === $domain ? null : (string) $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): void + { + if (!is_string($path)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Path'] = (string) $path; + } + + /** + * Maximum lifetime of the cookie in seconds. + * + * @return int|null + */ + public function getMaxAge() + { + return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie. + * + * @param int|null $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge): void + { + if (!is_int($maxAge) && null !== $maxAge) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires. + * + * @return string|int|null + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire. + * + * @param int|string|null $timestamp Unix timestamp or any English textual datetime description. + */ + public function setExpires($timestamp): void + { + if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp)); + } + + /** + * Get whether or not this is a secure cookie. + * + * @return bool + */ + 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): void + { + if (!is_bool($secure)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Secure'] = (bool) $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): void + { + if (!is_bool($discard)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['Discard'] = (bool) $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): void + { + if (!is_bool($httpOnly)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->data['HttpOnly'] = (bool) $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 + */ + public function matchesPath(string $requestPath): bool + { + $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 + */ + public function matchesDomain(string $domain): bool + { + $cookieDomain = $this->getDomain(); + if (null === $cookieDomain) { + return true; + } + + // Remove the leading '.' as per spec in RFC 6265. + // https://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = \ltrim(\strtolower($cookieDomain), '.'); + + $domain = \strtolower($domain); + + // Domain not set or exact match. + if ('' === $cookieDomain || $domain === $cookieDomain) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // https://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. + */ + public function isExpired(): bool + { + 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() + { + $name = $this->getName(); + if ($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 null. 0 and empty string are valid. Empty strings + // are technically against RFC 6265, but known to happen in the wild. + $value = $this->getValue(); + if ($value === null) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0. "0" is not a valid internet + // domain, but may be used as server name in a private network. + $domain = $this->getDomain(); + if ($domain === null || $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..a80956c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,39 @@ +request = $request; + $this->handlerContext = $handlerContext; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * 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. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php b/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php new file mode 100644 index 0000000..fa3ed69 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.php @@ -0,0 +1,9 @@ +getStatusCode() : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + + /** + * Wrap non-RequestExceptions with a RequestException + */ + public static function wrapException(RequestInterface $request, \Throwable $e): RequestException + { + 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 sent + * @param ResponseInterface $response Response received + * @param \Throwable|null $previous Previous exception + * @param array $handlerContext Optional handler context + * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Throwable $previous = null, + array $handlerContext = [], + BodySummarizerInterface $bodySummarizer = null + ): self { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $handlerContext + ); + } + + $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->__toString(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + return new $className($message, $request, $response, $previous, $handlerContext); + } + + /** + * Obfuscates URI if there is a username and a password present + */ + private static function obfuscateUri(UriInterface $uri): UriInterface + { + $userInfo = $uri->getUserInfo(); + + if (false !== ($pos = \strpos($userInfo, ':'))) { + return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***'); + } + + return $uri; + } + + /** + * Get the request that caused the exception + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Get the associated response + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Check if a response was received + */ + public function hasResponse(): bool + { + 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. + */ + public function getHandlerContext(): array + { + return $this->handlerContext; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000..8055e06 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,10 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options): EasyHandle + { + 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): void + { + $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(RequestInterface, array): PromiseInterface $handler + * @param CurlFactoryInterface $factory Dictates how the handle is released + */ + public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + 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): void + { + $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 + ); + ($easy->options['on_stats'])($stats); + } + + /** + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + // 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): PromiseInterface + { + 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 ($easy->createResponseException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered while creating the response', + $easy->request, + $easy->response, + $easy->createResponseException, + $ctx + ) + ); + } + + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered during the on_headers event', + $easy->request, + $easy->response, + $easy->onHeadersException, + $ctx + ) + ); + } + + $message = \sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + $uriString = (string) $easy->request->getUri(); + if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) { + $message .= \sprintf(' for %s', $uriString); + } + + // 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 P\Create::rejectionFor($error); + } + + /** + * @return array + */ + private function getDefaultConf(EasyHandle $easy): array + { + $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): void + { + $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 https://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): void + { + $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] = static 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): void + { + 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(string $name, array &$options): void + { + foreach (\array_keys($options['_headers']) as $key) { + if (!\strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf): void + { + $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']) === true && + ($verifyLink = \readlink($options['verify'])) !== false && + \is_dir($verifyLink) + ) + ) { + $conf[\CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[\CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[\CURLOPT_ENCODING] = $accept; + } else { + // The empty string enables all available decoders and implicitly + // sets a matching 'Accept-Encoding' header. + $conf[\CURLOPT_ENCODING] = ''; + // But as the user did not specify any acceptable encodings we need + // to overwrite this implicit header with an empty one. + $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (!isset($options['sink'])) { + // Use a default temp stream if no sink was set. + $options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+'); + } + $sink = $options['sink']; + if (!\is_string($sink)) { + $sink = \GuzzleHttp\Psr7\Utils::streamFor($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] = static function ($ch, $write) use ($sink): int { + return $sink->write($write); + }; + + $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']) || !Utils::isHostInNoProxy($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}"); + } + # OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files. + # see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html + $ext = pathinfo($cert, \PATHINFO_EXTENSION); + if (preg_match('#^(der|p12)$#i', $ext)) { + $conf[\CURLOPT_SSLCERTTYPE] = strtoupper($ext); + } + $conf[\CURLOPT_SSLCERT] = $cert; + } + + if (isset($options['ssl_key'])) { + if (\is_array($options['ssl_key'])) { + if (\count($options['ssl_key']) === 2) { + [$sslKey, $conf[\CURLOPT_SSLKEYPASSWD]] = $options['ssl_key']; + } else { + [$sslKey] = $options['ssl_key']; + } + } + + $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] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use ($progress) { + $progress($downloadSize, $downloaded, $uploadSize, $uploaded); + }; + } + + if (!empty($options['debug'])) { + $conf[\CURLOPT_STDERR] = Utils::debugResource($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. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx): PromiseInterface + { + 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): callable + { + 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 static function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = \trim($h); + if ($value === '') { + $startingResponse = true; + try { + $easy->createResponse(); + } catch (\Exception $e) { + $easy->createResponseException = $e; + return -1; + } + 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..fe57ed5 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,25 @@ +factory = $options['handle_factory'] + ?? new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + 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..4356d02 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,262 @@ + An array of delay times, indexed by handle id in `addRequest`. + * + * @see CurlMultiHandler::addRequest + */ + private $delays = []; + + /** + * @var array An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt() + */ + private $options = []; + + /** + * This handler accepts the following options: + * + * - handle_factory: An optional factory used to create curl handles + * - select_timeout: Optional timeout (in seconds) to block before timing + * out while selecting curl handles. Defaults to 1 second. + * - options: An associative array of CURLMOPT_* options and + * corresponding values for curl_multi_setopt() + */ + public function __construct(array $options = []) + { + $this->factory = $options['handle_factory'] ?? new CurlFactory(50); + + if (isset($options['select_timeout'])) { + $this->selectTimeout = $options['select_timeout']; + } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { + @trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED); + $this->selectTimeout = (int) $selectTimeout; + } else { + $this->selectTimeout = 1; + } + + $this->options = $options['options'] ?? []; + } + + /** + * @param string $name + * + * @return resource|\CurlMultiHandle + * + * @throws \BadMethodCallException when another field as `_mh` will be gotten + * @throws \RuntimeException when curl can not initialize a multi handle + */ + public function __get($name) + { + if ($name !== '_mh') { + throw new \BadMethodCallException("Can not get other property as '_mh'."); + } + + $multiHandle = \curl_multi_init(); + + if (false === $multiHandle) { + throw new \RuntimeException('Can not initialize curl multi handle.'); + } + + $this->_mh = $multiHandle; + + foreach ($this->options as $option => $value) { + // A warning is raised in case of a wrong option. + curl_multi_setopt($this->_mh, $option, $value); + } + + return $this->_mh; + } + + public function __destruct() + { + if (isset($this->_mh)) { + \curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $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(): void + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = Utils::currentTime(); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + \curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\Utils::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(): void + { + $queue = P\Utils::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): void + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + \curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id): bool + { + if (!is_int($id)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + // 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(): void + { + while ($done = \curl_multi_info_read($this->_mh)) { + if ($done['msg'] !== \CURLMSG_DONE) { + // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216 + continue; + } + $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(): int + { + $currentTime = Utils::currentTime(); + $nextTime = \PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return ((int) \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..224344d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,112 @@ +headers); + + $normalizedKeys = Utils::normalizeHeaderKeys($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( + $status, + $headers, + $this->sink, + $ver, + $reason + ); + } + + /** + * @param string $name + * + * @return void + * + * @throws \BadMethodCallException + */ + 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/HeaderProcessor.php b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php new file mode 100644 index 0000000..a098884 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php @@ -0,0 +1,42 @@ +|null $queue The parameters to be passed to the append function, as an indexed array. + * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. + * @param callable|null $onRejected Callback to invoke when the return value is rejected. + */ + public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null) + { + $this->onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + // array_values included for BC + $this->append(...array_values($queue)); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay']) && \is_numeric($options['delay'])) { + \usleep((int) $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 = $response($request, $options); + } + + $response = $response instanceof \Throwable + ? P\Create::rejectionFor($response) + : P\Create::promiseFor($response); + + return $response->then( + function (?ResponseInterface $value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + ($this->onFulfilled)($value); + } + + if ($value !== null && 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 StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + ($this->onRejected)($reason); + } + return P\Create::rejectionFor($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + * + * @param mixed ...$values + */ + public function append(...$values): void + { + foreach ($values as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Throwable + || $value instanceof PromiseInterface + || \is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found ' . Utils::describeType($value)); + } + } + } + + /** + * Get the last received request. + */ + public function getLastRequest(): ?RequestInterface + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + */ + public function getLastOptions(): array + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + */ + public function count(): int + { + return \count($this->queue); + } + + public function reset(): void + { + $this->queue = []; + } + + /** + * @param mixed $reason Promise or reason. + */ + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ): void { + if (isset($options['on_stats'])) { + $transferTime = $options['transfer_time'] ?? 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); + ($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..f045b52 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,51 @@ +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 (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed + || false !== \strpos($message, 'Connection refused') + || false !== \strpos($message, "couldn't connect to host") // error on HHVM + || false !== \strpos($message, "connection attempt failed") + ) { + $e = new ConnectException($e->getMessage(), $request, $e); + } else { + $e = RequestException::wrapException($request, $e); + } + $this->invokeStats($options, $request, $startTime, null, $e); + + return P\Create::rejectionFor($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + ?float $startTime, + ResponseInterface $response = null, + \Throwable $error = null + ): void { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []); + ($options['on_stats'])($stats); + } + } + + /** + * @param resource $stream + */ + private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface + { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + + try { + [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + [$stream, $headers] = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\Utils::streamFor($stream); + $sink = $stream; + + if (\strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + + try { + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered while creating the response', $request, null, $e) + ); + } + + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Exception $e) { + return P\Create::rejectionFor( + new RequestException('An error was encountered during the on_headers event', $request, $response, $e) + ); + } + } + + // 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): StreamInterface + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+'); + + return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink); + } + + /** + * @param resource $stream + */ + private function checkDecode(array $options, array $headers, $stream): array + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($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 string $contentLength Header specifying the amount of + * data to read. + * + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface + { + // 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\Utils::copyToStream( + $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 = []; + \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + try { + $resource = $callback(); + } finally { + \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; + } + + /** + * @return resource + */ + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = \array_flip(\get_class_methods(__CLASS__)); + } + + if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) { + throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request); + } + + // 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'][2]) && 'ntlm' === $options['auth'][2]) { + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + + $uri = $this->resolveHost($request, $options); + + $contextResource = $this->createResource( + static function () use ($context, $params) { + return \stream_context_create($context, $params); + } + ); + + return $this->createResource( + function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { + $resource = @\fopen((string) $uri, 'r', false, $contextResource); + $this->lastHeaders = $http_response_header ?? []; + + if (false === $resource) { + throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context); + } + + 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): UriInterface + { + $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 (false === $records || !isset($records[0]['ip'])) { + throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + } + return $uri->withHost($records[0]['ip']); + } + if ('v6' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_AAAA); + if (false === $records || !isset($records[0]['ipv6'])) { + throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + } + return $uri->withHost('[' . $records[0]['ipv6'] . ']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request): array + { + $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, + ], + 'ssl' => [ + 'peer_name' => $request->getUri()->getHost(), + ], + ]; + + $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; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void + { + $uri = null; + + if (!\is_array($value)) { + $uri = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) { + $uri = $value[$scheme]; + } + } + } + + if (!$uri) { + return; + } + + $parsed = $this->parse_proxy($uri); + $options['http']['proxy'] = $parsed['proxy']; + + if ($parsed['auth']) { + if (!isset($options['http']['header'])) { + $options['http']['header'] = []; + } + $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}"; + } + } + + /** + * Parses the given proxy URL to make it compatible with the format PHP's stream context expects. + */ + private function parse_proxy(string $url): array + { + $parsed = \parse_url($url); + + if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') { + if (isset($parsed['host']) && isset($parsed['port'])) { + $auth = null; + if (isset($parsed['user']) && isset($parsed['pass'])) { + $auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}"); + } + + return [ + 'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}", + 'auth' => $auth ? "Basic {$auth}" : null, + ]; + } + } + + // Return proxy as-is. + return [ + 'proxy' => $url, + 'auth' => null, + ]; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + + return; + } + + if (\is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!\file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value !== true) { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void + { + 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; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void + { + self::addNotification( + $params, + static function ($code, $a, $b, $c, $transferred, $total) use ($value) { + if ($code == \STREAM_NOTIFY_PROGRESS) { + // The upload progress cannot be determined. Use 0 for cURL compatibility: + // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html + $value($total, $transferred, 0, 0); + } + } + ); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void + { + 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 = Utils::debugResource($value); + $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); + self::addNotification( + $params, + static function (int $code, ...$passed) use ($ident, $value, $map, $args): void { + \fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (\array_filter($passed) as $i => $v) { + \fwrite($value, $args[$i] . ': "' . $v . '" '); + } + \fwrite($value, "\n"); + } + ); + } + + private static function addNotification(array &$params, callable $notify): void + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = self::callArray([ + $params['notification'], + $notify + ]); + } + } + + private static function callArray(array $functions): callable + { + return static function (...$args) use ($functions) { + foreach ($functions as $fn) { + $fn(...$args); + } + }; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/HandlerStack.php b/vendor/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 0000000..e0a1d11 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,275 @@ +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(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @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 !== null) { + $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(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler): void + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + */ + public function hasHandler(): bool + { + return $this->handler !== null ; + } + + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, ?string $name = null): void + { + \array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * Push a middleware to the top of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, string $name = ''): void + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after(string $findName, callable $middleware, string $withName = ''): void + { + $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): void + { + if (!is_string($remove) && !is_callable($remove)) { + trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + + $this->cached = null; + $idx = \is_callable($remove) ? 0 : 1; + $this->stack = \array_values(\array_filter( + $this->stack, + static function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable(RequestInterface, array): PromiseInterface + */ + public function resolve(): callable + { + if ($this->cached === null) { + if (($prev = $this->handler) === null) { + throw new \LogicException('No handler has been specified'); + } + + foreach (\array_reverse($this->stack) as $fn) { + /** @var callable(RequestInterface, array): PromiseInterface $prev */ + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + private function findByName(string $name): int + { + 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. + */ + private function splice(string $findName, string $withName, callable $middleware, bool $before): void + { + $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 callable|string $fn Function to write as a string. + */ + private function debugCallable($fn): string + { + 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]}'])"; + } + + /** @var object $fn */ + 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..da49954 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,198 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + public 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(?string $template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface|null $response Response that was received + * @param \Throwable|null $error Exception that was received + */ + public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string + { + $cache = []; + + /** @var string */ + 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\Message::toString($request); + break; + case 'response': + $result = $response ? Psr7\Message::toString($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()->__toString(); + break; + case 'res_body': + if (!$response instanceof ResponseInterface) { + $result = 'NULL'; + break; + } + + $body = $response->getBody(); + + if (!$body->isSeekable()) { + $result = 'RESPONSE_NOT_LOGGEABLE'; + break; + } + + $result = $response->getBody()->__toString(); + 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()->__toString(); + 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 + */ + private function headers(MessageInterface $message): string + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . \implode(', ', $values) . "\r\n"; + } + + return \trim($result); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php b/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php new file mode 100644 index 0000000..a39ac24 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/MessageFormatterInterface.php @@ -0,0 +1,18 @@ +withCookieHeader($request); + return $handler($request, $options) + ->then( + static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface { + $cookieJar->extractCookies($request, $response); + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_errors" request option is set to true. + * + * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages. + * + * @return callable(callable): callable Returns a function that accepts the next handler. + */ + public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable + { + return static function (callable $handler) use ($bodySummarizer): callable { + return static function ($request, array $options) use ($handler, $bodySummarizer) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then( + static function (ResponseInterface $response) use ($request, $bodySummarizer) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response, null, [], $bodySummarizer); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable(callable): callable Returns a function that accepts the next handler. + * + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container): callable + { + if (!\is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return static function (callable $handler) use (&$container): callable { + return static function (RequestInterface $request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + static function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options + ]; + return $value; + }, + static function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options + ]; + return P\Create::rejectionFor($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): callable + { + return static function (callable $handler) use ($before, $after): callable { + return static function (RequestInterface $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(): callable + { + return static function (callable $handler): RedirectMiddleware { + 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): callable + { + return static function (callable $handler) use ($decider, $delay): RetryMiddleware { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatterInterface|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, $formatter, string $logLevel = 'info'): callable + { + // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter + if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) { + throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class)); + } + + return static function (callable $handler) use ($logger, $formatter, $logLevel): callable { + return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then( + static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + return $response; + }, + static function ($reason) use ($logger, $request, $formatter): PromiseInterface { + $response = $reason instanceof RequestException ? $reason->getResponse() : null; + $message = $formatter->format($request, $response, P\Create::exceptionFor($reason)); + $logger->error($message); + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + */ + public static function prepareBody(): callable + { + return static function (callable $handler): PrepareBodyMiddleware { + 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. + */ + public static function mapRequest(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $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. + */ + public static function mapResponse(callable $fn): callable + { + return static function (callable $handler) use ($fn): callable { + return static function (RequestInterface $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..6277c61 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,125 @@ + $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 + */ + public function promise(): PromiseInterface + { + 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 = []): array + { + $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) + */ + private static function cmpCallback(array &$options, string $name, array &$results): void + { + if (!isset($options[$name])) { + $options[$name] = static function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = static 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..7ca6283 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,104 @@ +nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $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 (is_string($uri) && $type = Psr7\MimeType::fromFilename($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\Utils::modifyRequest($request, $modify), $options); + } + + /** + * Add expect header + */ + private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void + { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $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..f67d448 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,228 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** + * @var callable(RequestInterface, array): PromiseInterface + */ + private $nextHandler; + + /** + * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $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); + }); + } + + /** + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response) + { + if (\strpos((string) $response->getStatusCode(), '3') !== 0 + || !$response->hasHeader('Location') + ) { + return $response; + } + + $this->guardMax($request, $response, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + + // If authorization is handled by curl, unset it if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && defined('\CURLOPT_HTTPAUTH')) { + unset( + $options['curl'][\CURLOPT_HTTPAUTH], + $options['curl'][\CURLOPT_USERPWD] + ); + } + + if (isset($options['allow_redirects']['on_redirect'])) { + ($options['allow_redirects']['on_redirect'])( + $request, + $response, + $nextRequest->getUri() + ); + } + + $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. + */ + private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface + { + return $promise->then( + static 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, (string) $statusCode); + + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + /** + * Check for too many redirects. + * + * @throws TooManyRedirectsException Too many redirects. + */ + private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void + { + $current = $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, $response); + } + } + + public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface + { + // 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']) + ) { + $safeMethods = ['GET', 'HEAD', 'OPTIONS']; + $requestMethod = $request->getMethod(); + + $modify['method'] = in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET'; + $modify['body'] = ''; + } + + $uri = self::redirectUri($request, $response, $protocols); + if (isset($options['idn_conversion']) && ($options['idn_conversion'] !== false)) { + $idnOptions = ($options['idn_conversion'] === true) ? \IDNA_DEFAULT : $options['idn_conversion']; + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + + $modify['uri'] = $uri; + Psr7\Message::rewindBody($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 and Cookie headers if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) { + $modify['remove_headers'][] = 'Authorization'; + $modify['remove_headers'][] = 'Cookie'; + } + + return Psr7\Utils::modifyRequest($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header. + */ + private static function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ): UriInterface { + $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..20b31bc --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,264 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @return int milliseconds. + */ + public static function exponentialDelay(int $retries): int + { + return (int) \pow(2, $retries - 1) * 1000; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + 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 + */ + private function onFulfilled(RequestInterface $request, array $options): callable + { + return function ($value) use ($request, $options) { + if (!($this->decider)( + $options['retries'], + $request, + $value, + null + )) { + return $value; + } + return $this->doRetry($request, $options, $value); + }; + } + + /** + * Execute rejected closure + */ + private function onRejected(RequestInterface $req, array $options): callable + { + return function ($reason) use ($req, $options) { + if (!($this->decider)( + $options['retries'], + $req, + null, + $reason + )) { + return P\Create::rejectionFor($reason); + } + return $this->doRetry($req, $options); + }; + } + + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface + { + $options['delay'] = ($this->delay)(++$options['retries'], $response, $request); + + 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..93fa334 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,133 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Returns true if a response was received. + */ + public function hasResponse(): bool + { + 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. + */ + public function getEffectiveUri(): UriInterface + { + 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(): ?float + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + */ + public function getHandlerStats(): array + { + 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(string $stat) + { + return $this->handlerStats[$stat] ?? null; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Utils.php b/vendor/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 0000000..e355f32 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,385 @@ +getHost()) { + $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); + if ($asciiHost === false) { + $errorBitSet = $info['errors'] ?? 0; + + $errorConstants = array_filter(array_keys(get_defined_constants()), static function (string $name): bool { + 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); + } + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + + return $uri; + } + + /** + * @internal + */ + public static function getenv(string $name): ?string + { + if (isset($_SERVER[$name])) { + return (string) $_SERVER[$name]; + } + + if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== false && $value !== null) { + return (string) $value; + } + + return null; + } + + /** + * @return string|false + */ + private static function idnToAsci(string $domain, int $options, ?array &$info = []) + { + if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) { + return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info); + } + + throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 0000000..a70d2cb --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,167 @@ + +Copyright (c) 2015 Graham Campbell +Copyright (c) 2017 Tobias Schultze +Copyright (c) 2020 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/promises/README.md b/vendor/guzzlehttp/promises/README.md new file mode 100644 index 0000000..1ea667a --- /dev/null +++ b/vendor/guzzlehttp/promises/README.md @@ -0,0 +1,546 @@ +# Guzzle Promises + +[Promises/A+](https://promisesaplus.com/) implementation that handles promise +chaining and resolution iteratively, allowing for "infinite" promise chaining +while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) +for a general introduction to promises. + +- [Features](#features) +- [Quick start](#quick-start) +- [Synchronous wait](#synchronous-wait) +- [Cancellation](#cancellation) +- [API](#api) + - [Promise](#promise) + - [FulfilledPromise](#fulfilledpromise) + - [RejectedPromise](#rejectedpromise) +- [Promise interop](#promise-interop) +- [Implementation notes](#implementation-notes) + + +## Features + +- [Promises/A+](https://promisesaplus.com/) implementation. +- Promise resolution and chaining is handled iteratively, allowing for + "infinite" promise chaining. +- Promises have a synchronous `wait` method. +- Promises can be cancelled. +- Works with any object that has a `then` function. +- C# style async/await coroutine promises using + `GuzzleHttp\Promise\Coroutine::of()`. + + +## Quick Start + +A *promise* represents the eventual result of an asynchronous operation. The +primary way of interacting with a promise is through its `then` method, which +registers callbacks to receive either a promise's eventual value or the reason +why the promise cannot be fulfilled. + +### Callbacks + +Callbacks are registered with the `then` method by providing an optional +`$onFulfilled` followed by an optional `$onRejected` function. + + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then( + // $onFulfilled + function ($value) { + echo 'The promise was fulfilled.'; + }, + // $onRejected + function ($reason) { + echo 'The promise was rejected.'; + } +); +``` + +*Resolving* a promise means that you either fulfill a promise with a *value* or +reject a promise with a *reason*. Resolving a promise triggers callbacks +registered with the promise's `then` method. These callbacks are triggered +only once and in the order in which they were added. + +### Resolving a Promise + +Promises are fulfilled using the `resolve($value)` method. Resolving a promise +with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger +all of the onFulfilled callbacks (resolving a promise with a rejected promise +will reject the promise and trigger the `$onRejected` callbacks). + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(function ($value) { + // Return a value and don't break the chain + return "Hello, " . $value; + }) + // This then is executed after the first then and receives the value + // returned from the first then. + ->then(function ($value) { + echo $value; + }); + +// Resolving the promise triggers the $onFulfilled callbacks and outputs +// "Hello, reader." +$promise->resolve('reader.'); +``` + +### Promise Forwarding + +Promises can be chained one after the other. Each then in the chain is a new +promise. The return value of a promise is what's forwarded to the next +promise in the chain. Returning a promise in a `then` callback will cause the +subsequent promises in the chain to only be fulfilled when the returned promise +has been fulfilled. The next promise in the chain will be invoked with the +resolved value of the promise. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$nextPromise = new Promise(); + +$promise + ->then(function ($value) use ($nextPromise) { + echo $value; + return $nextPromise; + }) + ->then(function ($value) { + echo $value; + }); + +// Triggers the first callback and outputs "A" +$promise->resolve('A'); +// Triggers the second callback and outputs "B" +$nextPromise->resolve('B'); +``` + +### Promise Rejection + +When a promise is rejected, the `$onRejected` callbacks are invoked with the +rejection reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + echo $reason; +}); + +$promise->reject('Error!'); +// Outputs "Error!" +``` + +### Rejection Forwarding + +If an exception is thrown in an `$onRejected` callback, subsequent +`$onRejected` callbacks are invoked with the thrown exception as the reason. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + throw new Exception($reason); +})->then(null, function ($reason) { + assert($reason->getMessage() === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +You can also forward a rejection down the promise chain by returning a +`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or +`$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new Promise(); +$promise->then(null, function ($reason) { + return new RejectedPromise($reason); +})->then(null, function ($reason) { + assert($reason === 'Error!'); +}); + +$promise->reject('Error!'); +``` + +If an exception is not thrown in a `$onRejected` callback and the callback +does not return a rejected promise, downstream `$onFulfilled` callbacks are +invoked using the value returned from the `$onRejected` callback. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise(); +$promise + ->then(null, function ($reason) { + return "It's ok"; + }) + ->then(function ($value) { + assert($value === "It's ok"); + }); + +$promise->reject('Error!'); +``` + + +## Synchronous Wait + +You can synchronously force promises to complete using a promise's `wait` +method. When creating a promise, you can provide a wait function that is used +to synchronously force a promise to complete. When a wait function is invoked +it is expected to deliver a value to the promise or reject the promise. If the +wait function does not deliver a value, then an exception is thrown. The wait +function provided to a promise constructor is invoked when the `wait` function +of the promise is called. + +```php +$promise = new Promise(function () use (&$promise) { + $promise->resolve('foo'); +}); + +// Calling wait will return the value of the promise. +echo $promise->wait(); // outputs "foo" +``` + +If an exception is encountered while invoking the wait function of a promise, +the promise is rejected with the exception and the exception is thrown. + +```php +$promise = new Promise(function () use (&$promise) { + throw new Exception('foo'); +}); + +$promise->wait(); // throws the exception. +``` + +Calling `wait` on a promise that has been fulfilled will not trigger the wait +function. It will simply return the previously resolved value. + +```php +$promise = new Promise(function () { die('this is not called!'); }); +$promise->resolve('foo'); +echo $promise->wait(); // outputs "foo" +``` + +Calling `wait` on a promise that has been rejected will throw an exception. If +the rejection reason is an instance of `\Exception` the reason is thrown. +Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason +can be obtained by calling the `getReason` method of the exception. + +```php +$promise = new Promise(); +$promise->reject('foo'); +$promise->wait(); +``` + +> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' + +### Unwrapping a Promise + +When synchronously waiting on a promise, you are joining the state of the +promise into the current state of execution (i.e., return the value of the +promise if it was fulfilled or throw an exception if it was rejected). This is +called "unwrapping" the promise. Waiting on a promise will by default unwrap +the promise state. + +You can force a promise to resolve and *not* unwrap the state of the promise +by passing `false` to the first argument of the `wait` function: + +```php +$promise = new Promise(); +$promise->reject('foo'); +// This will not throw an exception. It simply ensures the promise has +// been resolved. +$promise->wait(false); +``` + +When unwrapping a promise, the resolved value of the promise will be waited +upon until the unwrapped value is not a promise. This means that if you resolve +promise A with a promise B and unwrap promise A, the value returned by the +wait function will be the value delivered to promise B. + +**Note**: when you do not unwrap the promise, no value is returned. + + +## Cancellation + +You can cancel a promise that has not yet been fulfilled using the `cancel()` +method of a promise. When creating a promise you can provide an optional +cancel function that when invoked cancels the action of computing a resolution +of the promise. + + +## API + +### Promise + +When creating a promise object, you can provide an optional `$waitFn` and +`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is +expected to resolve the promise. `$cancelFn` is a function with no arguments +that is expected to cancel the computation of a promise. It is invoked when the +`cancel()` method of a promise is called. + +```php +use GuzzleHttp\Promise\Promise; + +$promise = new Promise( + function () use (&$promise) { + $promise->resolve('waited'); + }, + function () { + // do something that will cancel the promise computation (e.g., close + // a socket, cancel a database query, etc...) + } +); + +assert('waited' === $promise->wait()); +``` + +A promise has the following methods: + +- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` + + Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. + +- `otherwise(callable $onRejected) : PromiseInterface` + + Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. + +- `wait($unwrap = true) : mixed` + + Synchronously waits on the promise to complete. + + `$unwrap` controls whether or not the value of the promise is returned for a + fulfilled promise or if an exception is thrown if the promise is rejected. + This is set to `true` by default. + +- `cancel()` + + Attempts to cancel the promise if possible. The promise being cancelled and + the parent most ancestor that has not yet been resolved will also be + cancelled. Any promises waiting on the cancelled promise to resolve will also + be cancelled. + +- `getState() : string` + + Returns the state of the promise. One of `pending`, `fulfilled`, or + `rejected`. + +- `resolve($value)` + + Fulfills the promise with the given `$value`. + +- `reject($reason)` + + Rejects the promise with the given `$reason`. + + +### FulfilledPromise + +A fulfilled promise can be created to represent a promise that has been +fulfilled. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +### RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +## Promise Interoperability + +This library works with foreign promises that have a `then` method. This means +you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) +for example. When a foreign promise is returned inside of a then method +callback, promise resolution will occur recursively. + +```php +// Create a React promise +$deferred = new React\Promise\Deferred(); +$reactPromise = $deferred->promise(); + +// Create a Guzzle promise that is fulfilled with a React promise. +$guzzlePromise = new GuzzleHttp\Promise\Promise(); +$guzzlePromise->then(function ($value) use ($reactPromise) { + // Do something something with the value... + // Return the React promise + return $reactPromise; +}); +``` + +Please note that wait and cancel chaining is no longer possible when forwarding +a foreign promise. You will need to wrap a third-party promise with a Guzzle +promise in order to utilize wait and cancel functions with foreign promises. + + +### Event Loop Integration + +In order to keep the stack size constant, Guzzle promises are resolved +asynchronously using a task queue. When waiting on promises synchronously, the +task queue will be automatically run to ensure that the blocking promise and +any forwarded promises are resolved. When using promises asynchronously in an +event loop, you will need to run the task queue on each tick of the loop. If +you do not run the task queue, then promises will not be resolved. + +You can run the task queue using the `run()` method of the global task queue +instance. + +```php +// Get the global task queue +$queue = GuzzleHttp\Promise\Utils::queue(); +$queue->run(); +``` + +For example, you could use Guzzle promises with React using a periodic timer: + +```php +$loop = React\EventLoop\Factory::create(); +$loop->addPeriodicTimer(0, [$queue, 'run']); +``` + +*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? + + +## Implementation Notes + +### Promise Resolution and Chaining is Handled Iteratively + +By shuffling pending handlers from one owner to another, promises are +resolved iteratively, allowing for "infinite" then chaining. + +```php +then(function ($v) { + // The stack size remains constant (a good thing) + echo xdebug_get_stack_depth() . ', '; + return $v + 1; + }); +} + +$parent->resolve(0); +var_dump($p->wait()); // int(1000) + +``` + +When a promise is fulfilled or rejected with a non-promise value, the promise +then takes ownership of the handlers of each child promise and delivers values +down the chain without using recursion. + +When a promise is resolved with another promise, the original promise transfers +all of its pending handlers to the new promise. When the new promise is +eventually resolved, all of the pending handlers are delivered the forwarded +value. + +### A Promise is the Deferred + +Some promise libraries implement promises using a deferred object to represent +a computation and a promise object to represent the delivery of the result of +the computation. This is a nice separation of computation and delivery because +consumers of the promise cannot modify the value that will be eventually +delivered. + +One side effect of being able to implement promise resolution and chaining +iteratively is that you need to be able for one promise to reach into the state +of another promise to shuffle around ownership of handlers. In order to achieve +this without making the handlers of a promise publicly mutable, a promise is +also the deferred value, allowing promises of the same parent class to reach +into and modify the private properties of promises of the same type. While this +does allow consumers of the value to modify the resolution or rejection of the +deferred, it is a small price to pay for keeping the stack size constant. + +```php +$promise = new Promise(); +$promise->then(function ($value) { echo $value; }); +// The promise is the deferred value, so you can deliver a value to it. +$promise->resolve('foo'); +// prints "foo" +``` + + +## Upgrading from Function API + +A static API was first introduced in 1.4.0, in order to mitigate problems with +functions conflicting between global and local copies of the package. The +function API will be removed in 2.0.0. A migration table has been provided here +for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `queue` | `Utils::queue` | +| `task` | `Utils::task` | +| `promise_for` | `Create::promiseFor` | +| `rejection_for` | `Create::rejectionFor` | +| `exception_for` | `Create::exceptionFor` | +| `iter_for` | `Create::iterFor` | +| `inspect` | `Utils::inspect` | +| `inspect_all` | `Utils::inspectAll` | +| `unwrap` | `Utils::unwrap` | +| `all` | `Utils::all` | +| `some` | `Utils::some` | +| `any` | `Utils::any` | +| `settle` | `Utils::settle` | +| `each` | `Each::of` | +| `each_limit` | `Each::ofLimit` | +| `each_limit_all` | `Each::ofLimitAll` | +| `!is_fulfilled` | `Is::pending` | +| `is_fulfilled` | `Is::fulfilled` | +| `is_rejected` | `Is::rejected` | +| `is_settled` | `Is::settled` | +| `coroutine` | `Coroutine::of` | + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information. + + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/promises/composer.json b/vendor/guzzlehttp/promises/composer.json new file mode 100644 index 0000000..c959fb3 --- /dev/null +++ b/vendor/guzzlehttp/promises/composer.json @@ -0,0 +1,58 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Promise\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/simple-phpunit", + "test-ci": "vendor/bin/simple-phpunit --coverage-text" + }, + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/guzzlehttp/promises/src/AggregateException.php b/vendor/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000..d2b5712 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,17 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * + * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + + /** + * @var Generator + */ + private $generator; + + /** + * @var Promise + */ + private $result; + + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function () { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + try { + $this->nextCoroutine($this->generator->current()); + } catch (\Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * Create a new coroutine. + * + * @return self + */ + public static function of(callable $generatorFn) + { + return new self($generatorFn); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected) + { + return $this->result->otherwise($onRejected); + } + + public function wait($unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState() + { + return $this->result->getState(); + } + + public function resolve($value) + { + $this->result->resolve($value); + } + + public function reject($reason) + { + $this->result->reject($reason); + } + + public function cancel() + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded) + { + $this->currentPromise = Create::promiseFor($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value) + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason) + { + unset($this->currentPromise); + try { + $nextYield = $this->generator->throw(Create::exceptionFor($reason)); + // The throw was caught, so keep iterating on the coroutine + $this->nextCoroutine($nextYield); + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/vendor/guzzlehttp/promises/src/Create.php b/vendor/guzzlehttp/promises/src/Create.php new file mode 100644 index 0000000..8d038e9 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Create.php @@ -0,0 +1,84 @@ +then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + + return new FulfilledPromise($value); + } + + /** + * Creates a rejected promise for a reason if the reason is not a promise. + * If the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + */ + public static function rejectionFor($reason) + { + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); + } + + /** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + */ + public static function exceptionFor($reason) + { + if ($reason instanceof \Exception || $reason instanceof \Throwable) { + return $reason; + } + + return new RejectionException($reason); + } + + /** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ + public static function iterFor($value) + { + if ($value instanceof \Iterator) { + return $value; + } + + if (is_array($value)) { + return new \ArrayIterator($value); + } + + return new \ArrayIterator([$value]); + } +} diff --git a/vendor/guzzlehttp/promises/src/Each.php b/vendor/guzzlehttp/promises/src/Each.php new file mode 100644 index 0000000..1dda354 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Each.php @@ -0,0 +1,90 @@ + $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); + } + + /** + * Like of, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow + * for dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + */ + public static function ofLimit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null + ) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); + } + + /** + * Like limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + */ + public static function ofLimitAll( + $iterable, + $concurrency, + callable $onFulfilled = null + ) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); + } +} diff --git a/vendor/guzzlehttp/promises/src/EachPromise.php b/vendor/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000..280d799 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,247 @@ +iterable = Create::iterFor($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + /** @psalm-suppress InvalidNullableReturnType */ + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + /** @psalm-assert Promise $this->aggregate */ + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } catch (\Exception $e) { + $this->aggregate->reject($e); + } + + /** + * @psalm-suppress NullableReturnStatement + * @phpstan-ignore-next-line + */ + return $this->aggregate; + } + + private function createPromise() + { + $this->mutex = false; + $this->aggregate = new Promise(function () { + if ($this->checkIfFinished()) { + return; + } + reset($this->pending); + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = current($this->pending)) { + next($this->pending); + $promise->wait(); + if (Is::settled($this->aggregate)) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + $this->nextPendingIndex = 0; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()); + return; + } + + // Add only up to N pending promises. + $concurrency = is_callable($this->concurrency) + ? call_user_func($this->concurrency, count($this->pending)) + : $this->concurrency; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()); + } + + private function addPending() + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = Create::promiseFor($this->iterable->current()); + $key = $this->iterable->key(); + + // Iterable keys may not be unique, so we use a counter to + // guarantee uniqueness + $idx = $this->nextPendingIndex++; + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx, $key) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, + $value, + $key, + $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx, $key) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, + $reason, + $key, + $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return false; + } + + $this->mutex = true; + + try { + $this->iterable->next(); + $this->mutex = false; + return true; + } catch (\Throwable $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } catch (\Exception $e) { + $this->aggregate->reject($e); + $this->mutex = false; + return false; + } + } + + private function step($idx) + { + // If the promise was already resolved, then ignore this step. + if (Is::settled($this->aggregate)) { + return; + } + + unset($this->pending[$idx]); + + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished() + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return true; + } + + return false; + } +} diff --git a/vendor/guzzlehttp/promises/src/FulfilledPromise.php b/vendor/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 0000000..98f72a6 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,84 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = Utils::queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if (Is::pending($p)) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/Is.php b/vendor/guzzlehttp/promises/src/Is.php new file mode 100644 index 0000000..c3ed8d0 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Is.php @@ -0,0 +1,46 @@ +getState() === PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + */ + public static function settled(PromiseInterface $promise) + { + return $promise->getState() !== PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled. + * + * @return bool + */ + public static function fulfilled(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + + /** + * Returns true if a promise is rejected. + * + * @return bool + */ + public static function rejected(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/vendor/guzzlehttp/promises/src/Promise.php b/vendor/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000..7593905 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,278 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + return $p; + } + + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + $promise = Create::promiseFor($this->result); + return $onFulfilled ? $promise->then($onFulfilled) : $promise; + } + + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = Create::rejectionFor($this->result); + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true) + { + $this->waitIfPending(); + + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } + if ($unwrap) { + if ($this->state === self::FULFILLED) { + return $this->result; + } + // It's rejected so "unwrap" and throw an exception. + throw Create::exceptionFor($this->result); + } + } + + public function getState() + { + return $this->state; + } + + public function cancel() + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } catch (\Exception $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!is_object($value) || !method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + Utils::queue()->add(static function () use ($id, $value, $handlers) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise && Is::pending($value)) { + // We can just merge our handlers onto the next promise. + $value->handlers = array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then( + static function ($value) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + */ + private static function callHandler($index, $value, array $handler) + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if (Is::settled($promise)) { + return; + } + + try { + if (isset($handler[$index])) { + /* + * If $f throws an exception, then $handler will be in the exception + * stack trace. Since $handler contains a reference to the callable + * itself we get a circular reference. We clear the $handler + * here to avoid that memory leak. + */ + $f = $handler[$index]; + unset($handler); + $promise->resolve($f($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's no wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' + . 'no internal wait function. You must provide a wait ' + . 'function when constructing the promise to be able to ' + . 'wait on a promise.'); + } + + Utils::queue()->run(); + + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList() + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + do { + $result->waitIfPending(); + $result = $result->result; + } while ($result instanceof Promise); + + if ($result instanceof PromiseInterface) { + $result->wait(false); + } + } + } +} diff --git a/vendor/guzzlehttp/promises/src/PromiseInterface.php b/vendor/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000..e598331 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,97 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = Utils::queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if (Is::pending($p)) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Throwable $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } catch (\Exception $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw Create::exceptionFor($this->reason); + } + + return null; + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/vendor/guzzlehttp/promises/src/RejectionException.php b/vendor/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 0000000..e2f1377 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,48 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' + . json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueue.php b/vendor/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 0000000..f0fba2c --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,67 @@ +run(); + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + public function isEmpty() + { + return !$this->queue; + } + + public function add(callable $task) + { + $this->queue[] = $task; + } + + public function run() + { + while ($task = array_shift($this->queue)) { + /** @var callable $task */ + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() + { + $this->enableShutdown = false; + } +} diff --git a/vendor/guzzlehttp/promises/src/TaskQueueInterface.php b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 0000000..723d4d5 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,24 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\Utils::queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + */ + public static function queue(TaskQueueInterface $assign = null) + { + static $queue; + + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; + } + + /** + * Adds a function to run in the task queue when it is next `run()` and + * returns a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + */ + public static function task(callable $task) + { + $queue = self::queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + if (Is::pending($promise)) { + $promise->resolve($task()); + } + } catch (\Throwable $e) { + $promise->reject($e); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; + } + + /** + * Synchronously waits on a promise to resolve and returns an inspection + * state array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the + * array will contain a "value" key mapping to the fulfilled value of the + * promise. If the promise is rejected, the array will contain a "reason" + * key mapping to the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + */ + public static function inspect(PromiseInterface $promise) + { + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait() + ]; + } catch (RejectionException $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } catch (\Throwable $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } catch (\Exception $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } + } + + /** + * Waits on all of the provided promises, but does not unwrap rejected + * promises as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + */ + public static function inspectAll($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = inspect($promise); + } + + return $results; + } + + /** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same + * order the promises were provided). An exception is thrown if any of the + * promises are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + */ + public static function unwrap($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; + } + + /** + * Given an array of promises, return a promise that is fulfilled when all + * the items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + * + * @return PromiseInterface + */ + public static function all($promises, $recursive = false) + { + $results = []; + $promise = Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $aggregate->reject($reason); + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + + if (true === $recursive) { + $promise = $promise->then(function ($results) use ($recursive, &$promises) { + foreach ($promises as $promise) { + if (Is::pending($promise)) { + return self::all($promises, $recursive); + } + } + return $results; + }); + } + + return $promise; + } + + /** + * Initiate a competitive race between multiple promises or values (values + * will become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise + * is fulfilled with an array that contains the fulfillment values of the + * winners in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number + * of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function some($count, $promises) + { + $results = []; + $rejections = []; + + return Each::of( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if (Is::settled($p)) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + return array_values($results); + } + ); + } + + /** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function any($promises) + { + return self::some(1, $promises)->then(function ($values) { + return $values[0]; + }); + } + + /** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + */ + public static function settle($promises) + { + $results = []; + + return Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + } +} diff --git a/vendor/guzzlehttp/promises/src/functions.php b/vendor/guzzlehttp/promises/src/functions.php new file mode 100644 index 0000000..c03d39d --- /dev/null +++ b/vendor/guzzlehttp/promises/src/functions.php @@ -0,0 +1,363 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + * + * @deprecated queue will be removed in guzzlehttp/promises:2.0. Use Utils::queue instead. + */ +function queue(TaskQueueInterface $assign = null) +{ + return Utils::queue($assign); +} + +/** + * Adds a function to run in the task queue when it is next `run()` and returns + * a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + * + * @return PromiseInterface + * + * @deprecated task will be removed in guzzlehttp/promises:2.0. Use Utils::task instead. + */ +function task(callable $task) +{ + return Utils::task($task); +} + +/** + * Creates a promise for a value if the value is not a promise. + * + * @param mixed $value Promise or value. + * + * @return PromiseInterface + * + * @deprecated promise_for will be removed in guzzlehttp/promises:2.0. Use Create::promiseFor instead. + */ +function promise_for($value) +{ + return Create::promiseFor($value); +} + +/** + * Creates a rejected promise for a reason if the reason is not a promise. If + * the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + * + * @return PromiseInterface + * + * @deprecated rejection_for will be removed in guzzlehttp/promises:2.0. Use Create::rejectionFor instead. + */ +function rejection_for($reason) +{ + return Create::rejectionFor($reason); +} + +/** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + * + * @deprecated exception_for will be removed in guzzlehttp/promises:2.0. Use Create::exceptionFor instead. + */ +function exception_for($reason) +{ + return Create::exceptionFor($reason); +} + +/** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + * + * @deprecated iter_for will be removed in guzzlehttp/promises:2.0. Use Create::iterFor instead. + */ +function iter_for($value) +{ + return Create::iterFor($value); +} + +/** + * Synchronously waits on a promise to resolve and returns an inspection state + * array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the array + * will contain a "value" key mapping to the fulfilled value of the promise. If + * the promise is rejected, the array will contain a "reason" key mapping to + * the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspect instead. + */ +function inspect(PromiseInterface $promise) +{ + return Utils::inspect($promise); +} + +/** + * Waits on all of the provided promises, but does not unwrap rejected promises + * as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + * + * @return array + * + * @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspectAll instead. + */ +function inspect_all($promises) +{ + return Utils::inspectAll($promises); +} + +/** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same order + * the promises were provided). An exception is thrown if any of the promises + * are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @return array + * + * @throws \Exception on error + * @throws \Throwable on error in PHP >=7 + * + * @deprecated unwrap will be removed in guzzlehttp/promises:2.0. Use Utils::unwrap instead. + */ +function unwrap($promises) +{ + return Utils::unwrap($promises); +} + +/** + * Given an array of promises, return a promise that is fulfilled when all the + * items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + * + * @return PromiseInterface + * + * @deprecated all will be removed in guzzlehttp/promises:2.0. Use Utils::all instead. + */ +function all($promises, $recursive = false) +{ + return Utils::all($promises, $recursive); +} + +/** + * Initiate a competitive race between multiple promises or values (values will + * become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise is + * fulfilled with an array that contains the fulfillment values of the winners + * in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number of + * fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated some will be removed in guzzlehttp/promises:2.0. Use Utils::some instead. + */ +function some($count, $promises) +{ + return Utils::some($count, $promises); +} + +/** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated any will be removed in guzzlehttp/promises:2.0. Use Utils::any instead. + */ +function any($promises) +{ + return Utils::any($promises); +} + +/** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + * + * @return PromiseInterface + * + * @deprecated settle will be removed in guzzlehttp/promises:2.0. Use Utils::settle instead. + */ +function settle($promises) +{ + return Utils::settle($promises); +} + +/** + * Given an iterator that yields promises or values, returns a promise that is + * fulfilled with a null value when the iterator has been consumed or the + * aggregate promise has been fulfilled or rejected. + * + * $onFulfilled is a function that accepts the fulfilled value, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * $onRejected is a function that accepts the rejection reason, iterator index, + * and the aggregate promise. The callback can invoke any necessary side + * effects and choose to resolve or reject the aggregate if needed. + * + * @param mixed $iterable Iterator or array to iterate over. + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + * + * @deprecated each will be removed in guzzlehttp/promises:2.0. Use Each::of instead. + */ +function each( + $iterable, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::of($iterable, $onFulfilled, $onRejected); +} + +/** + * Like each, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow for + * dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * @param callable $onRejected + * + * @return PromiseInterface + * + * @deprecated each_limit will be removed in guzzlehttp/promises:2.0. Use Each::ofLimit instead. + */ +function each_limit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null +) { + return Each::ofLimit($iterable, $concurrency, $onFulfilled, $onRejected); +} + +/** + * Like each_limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + * + * @deprecated each_limit_all will be removed in guzzlehttp/promises:2.0. Use Each::ofLimitAll instead. + */ +function each_limit_all( + $iterable, + $concurrency, + callable $onFulfilled = null +) { + return Each::ofLimitAll($iterable, $concurrency, $onFulfilled); +} + +/** + * Returns true if a promise is fulfilled. + * + * @return bool + * + * @deprecated is_fulfilled will be removed in guzzlehttp/promises:2.0. Use Is::fulfilled instead. + */ +function is_fulfilled(PromiseInterface $promise) +{ + return Is::fulfilled($promise); +} + +/** + * Returns true if a promise is rejected. + * + * @return bool + * + * @deprecated is_rejected will be removed in guzzlehttp/promises:2.0. Use Is::rejected instead. + */ +function is_rejected(PromiseInterface $promise) +{ + return Is::rejected($promise); +} + +/** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + * + * @deprecated is_settled will be removed in guzzlehttp/promises:2.0. Use Is::settled instead. + */ +function is_settled(PromiseInterface $promise) +{ + return Is::settled($promise); +} + +/** + * Create a new coroutine. + * + * @see Coroutine + * + * @return PromiseInterface + * + * @deprecated coroutine will be removed in guzzlehttp/promises:2.0. Use Coroutine::of instead. + */ +function coroutine(callable $generatorFn) +{ + return Coroutine::of($generatorFn); +} diff --git a/vendor/guzzlehttp/promises/src/functions_include.php b/vendor/guzzlehttp/promises/src/functions_include.php new file mode 100644 index 0000000..34cd171 --- /dev/null +++ b/vendor/guzzlehttp/promises/src/functions_include.php @@ -0,0 +1,6 @@ +withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` + +### Deprecated + +- `Uri::resolve` in favor of `UriResolver::resolve` +- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +### Fixed + +- `Stream::read` when length parameter <= 0. +- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. +- `ServerRequest::getUriFromGlobals` when `Host` header contains port. +- Compatibility of URIs with `file` scheme and empty host. + + +## [1.3.1] - 2016-06-25 + +### Fixed + +- `Uri::__toString` for network path references, e.g. `//example.org`. +- Missing lowercase normalization for host. +- Handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +- `Uri::withAddedHeader` to correctly merge headers with different case. +- Trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +- `Uri::withAddedHeader` with an array of header values. +- `Uri::resolve` when base path has no slash and handling of fragment. +- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +- `ServerRequest::withoutAttribute` when attribute value is null. + + +## [1.3.0] - 2016-04-13 + +### Added + +- Remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +- Support for stream_for from scalars. + +### Changed + +- Can now extend Uri. + +### Fixed +- A bug in validating request methods by making it more permissive. + + +## [1.2.3] - 2016-02-18 + +### Fixed + +- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +- Handling of gzipped responses with FNAME headers. + + +## [1.2.2] - 2016-01-22 + +### Added + +- Support for URIs without any authority. +- Support for HTTP 451 'Unavailable For Legal Reasons.' +- Support for using '0' as a filename. +- Support for including non-standard ports in Host headers. + + +## [1.2.1] - 2015-11-02 + +### Changes + +- Now supporting negative offsets when seeking to SEEK_END. + + +## [1.2.0] - 2015-08-15 + +### Changed + +- Body as `"0"` is now properly added to a response. +- Now allowing forward seeking in CachingStream. +- Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +- functions.php is now conditionally required. +- user-info is no longer dropped when resolving URIs. + + +## [1.1.0] - 2015-06-24 + +### Changed + +- URIs can now be relative. +- `multipart/form-data` headers are now overridden case-insensitively. +- URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +- A port is no longer added to a URI when the scheme is missing and no port is + present. + + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` + + + +[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0 +[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0 +[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0 diff --git a/vendor/guzzlehttp/psr7/LICENSE b/vendor/guzzlehttp/psr7/LICENSE new file mode 100644 index 0000000..51c7ec8 --- /dev/null +++ b/vendor/guzzlehttp/psr7/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michael Dowling +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Graham Campbell +Copyright (c) 2016 Tobias Schultze +Copyright (c) 2016 George Mponos +Copyright (c) 2018 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/psr7/README.md b/vendor/guzzlehttp/psr7/README.md new file mode 100644 index 0000000..8b9929a --- /dev/null +++ b/vendor/guzzlehttp/psr7/README.md @@ -0,0 +1,872 @@ +# PSR-7 Message Implementation + +This repository contains a full [PSR-7](https://www.php-fig.org/psr/psr-7/) +message implementation, several stream decorators, and some helpful +functionality like query string parsing. + +![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg) +![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg) + + +# Stream implementation + +This package comes with a number of stream implementations and stream +decorators. + + +## AppendStream + +`GuzzleHttp\Psr7\AppendStream` + +Reads from multiple streams, one after the other. + +```php +use GuzzleHttp\Psr7; + +$a = Psr7\Utils::streamFor('abc, '); +$b = Psr7\Utils::streamFor('123.'); +$composed = new Psr7\AppendStream([$a, $b]); + +$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); + +echo $composed; // abc, 123. Above all listen to me. +``` + + +## BufferStream + +`GuzzleHttp\Psr7\BufferStream` + +Provides a buffer stream that can be written to fill a buffer, and read +from to remove bytes from the buffer. + +This stream returns a "hwm" metadata value that tells upstream consumers +what the configured high water mark of the stream is, or the maximum +preferred size of the buffer. + +```php +use GuzzleHttp\Psr7; + +// When more than 1024 bytes are in the buffer, it will begin returning +// false to writes. This is an indication that writers should slow down. +$buffer = new Psr7\BufferStream(1024); +``` + + +## CachingStream + +The CachingStream is used to allow seeking over previously read bytes on +non-seekable streams. This can be useful when transferring a non-seekable +entity body fails due to needing to rewind the stream (for example, resulting +from a redirect). Data that is read from the remote stream will be buffered in +a PHP temp stream so that previously read bytes are cached first in memory, +then on disk. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); +$stream = new Psr7\CachingStream($original); + +$stream->read(1024); +echo $stream->tell(); +// 1024 + +$stream->seek(0); +echo $stream->tell(); +// 0 +``` + + +## DroppingStream + +`GuzzleHttp\Psr7\DroppingStream` + +Stream decorator that begins dropping data once the size of the underlying +stream becomes too full. + +```php +use GuzzleHttp\Psr7; + +// Create an empty stream +$stream = Psr7\Utils::streamFor(); + +// Start dropping data when the stream has more than 10 bytes +$dropping = new Psr7\DroppingStream($stream, 10); + +$dropping->write('01234567890123456789'); +echo $stream; // 0123456789 +``` + + +## FnStream + +`GuzzleHttp\Psr7\FnStream` + +Compose stream implementations based on a hash of functions. + +Allows for easy testing and extension of a provided stream without needing +to create a concrete class for a simple extension point. + +```php + +use GuzzleHttp\Psr7; + +$stream = Psr7\Utils::streamFor('hi'); +$fnStream = Psr7\FnStream::decorate($stream, [ + 'rewind' => function () use ($stream) { + echo 'About to rewind - '; + $stream->rewind(); + echo 'rewound!'; + } +]); + +$fnStream->rewind(); +// Outputs: About to rewind - rewound! +``` + + +## InflateStream + +`GuzzleHttp\Psr7\InflateStream` + +Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content. + +This stream decorator converts the provided stream to a PHP stream resource, +then appends the zlib.inflate filter. The stream is then converted back +to a Guzzle stream resource to be used as a Guzzle stream. + + +## LazyOpenStream + +`GuzzleHttp\Psr7\LazyOpenStream` + +Lazily reads or writes to a file that is opened only after an IO operation +take place on the stream. + +```php +use GuzzleHttp\Psr7; + +$stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); +// The file has not yet been opened... + +echo $stream->read(10); +// The file is opened and read from only when needed. +``` + + +## LimitStream + +`GuzzleHttp\Psr7\LimitStream` + +LimitStream can be used to read a subset or slice of an existing stream object. +This can be useful for breaking a large file into smaller pieces to be sent in +chunks (e.g. Amazon S3's multipart upload API). + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); +echo $original->getSize(); +// >>> 1048576 + +// Limit the size of the body to 1024 bytes and start reading from byte 2048 +$stream = new Psr7\LimitStream($original, 1024, 2048); +echo $stream->getSize(); +// >>> 1024 +echo $stream->tell(); +// >>> 0 +``` + + +## MultipartStream + +`GuzzleHttp\Psr7\MultipartStream` + +Stream that when read returns bytes for a streaming multipart or +multipart/form-data stream. + + +## NoSeekStream + +`GuzzleHttp\Psr7\NoSeekStream` + +NoSeekStream wraps a stream and does not allow seeking. + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); +$noSeek = new Psr7\NoSeekStream($original); + +echo $noSeek->read(3); +// foo +var_export($noSeek->isSeekable()); +// false +$noSeek->seek(0); +var_export($noSeek->read(3)); +// NULL +``` + + +## PumpStream + +`GuzzleHttp\Psr7\PumpStream` + +Provides a read only stream that pumps data from a PHP callable. + +When invoking the provided callable, the PumpStream will pass the amount of +data requested to read to the callable. The callable can choose to ignore +this value and return fewer or more bytes than requested. Any extra data +returned by the provided callable is buffered internally until drained using +the read() function of the PumpStream. The provided callable MUST return +false when there is no more data to read. + + +## Implementing stream decorators + +Creating a stream decorator is very easy thanks to the +`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that +implement `Psr\Http\Message\StreamInterface` by proxying to an underlying +stream. Just `use` the `StreamDecoratorTrait` and implement your custom +methods. + +For example, let's say we wanted to call a specific function each time the last +byte is read from a stream. This could be implemented by overriding the +`read()` method. + +```php +use Psr\Http\Message\StreamInterface; +use GuzzleHttp\Psr7\StreamDecoratorTrait; + +class EofCallbackStream implements StreamInterface +{ + use StreamDecoratorTrait; + + private $callback; + + public function __construct(StreamInterface $stream, callable $cb) + { + $this->stream = $stream; + $this->callback = $cb; + } + + public function read($length) + { + $result = $this->stream->read($length); + + // Invoke the callback when EOF is hit. + if ($this->eof()) { + call_user_func($this->callback); + } + + return $result; + } +} +``` + +This decorator could be added to any existing stream and used like so: + +```php +use GuzzleHttp\Psr7; + +$original = Psr7\Utils::streamFor('foo'); + +$eofStream = new EofCallbackStream($original, function () { + echo 'EOF!'; +}); + +$eofStream->read(2); +$eofStream->read(1); +// echoes "EOF!" +$eofStream->seek(0); +$eofStream->read(3); +// echoes "EOF!" +``` + + +## PHP StreamWrapper + +You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a +PSR-7 stream as a PHP stream resource. + +Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP +stream from a PSR-7 stream. + +```php +use GuzzleHttp\Psr7\StreamWrapper; + +$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); +$resource = StreamWrapper::getResource($stream); +echo fread($resource, 6); // outputs hello! +``` + + +# Static API + +There are various static methods available under the `GuzzleHttp\Psr7` namespace. + + +## `GuzzleHttp\Psr7\Message::toString` + +`public static function toString(MessageInterface $message): string` + +Returns the string representation of an HTTP message. + +```php +$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); +echo GuzzleHttp\Psr7\Message::toString($request); +``` + + +## `GuzzleHttp\Psr7\Message::bodySummary` + +`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null` + +Get a short summary of the message body. + +Will return `null` if the response is not printable. + + +## `GuzzleHttp\Psr7\Message::rewindBody` + +`public static function rewindBody(MessageInterface $message): void` + +Attempts to rewind a message body and throws an exception on failure. + +The body of the message will only be rewound if a call to `tell()` +returns a value other than `0`. + + +## `GuzzleHttp\Psr7\Message::parseMessage` + +`public static function parseMessage(string $message): array` + +Parses an HTTP message into an associative array. + +The array contains the "start-line" key containing the start line of +the message, "headers" key containing an associative array of header +array values, and a "body" key containing the body of the message. + + +## `GuzzleHttp\Psr7\Message::parseRequestUri` + +`public static function parseRequestUri(string $path, array $headers): string` + +Constructs a URI for an HTTP request message. + + +## `GuzzleHttp\Psr7\Message::parseRequest` + +`public static function parseRequest(string $message): Request` + +Parses a request message string into a request object. + + +## `GuzzleHttp\Psr7\Message::parseResponse` + +`public static function parseResponse(string $message): Response` + +Parses a response message string into a response object. + + +## `GuzzleHttp\Psr7\Header::parse` + +`public static function parse(string|array $header): array` + +Parse an array of header values containing ";" separated data into an +array of associative arrays representing the header key value pair data +of the header. When a parameter does not contain a value, but just +contains a key, this function will inject a key with a '' string value. + + +## `GuzzleHttp\Psr7\Header::splitList` + +`public static function splitList(string|string[] $header): string[]` + +Splits a HTTP header defined to contain a comma-separated list into +each individual value: + +``` +$knownEtags = Header::splitList($request->getHeader('if-none-match')); +``` + +Example headers include `accept`, `cache-control` and `if-none-match`. + + +## `GuzzleHttp\Psr7\Header::normalize` (deprecated) + +`public static function normalize(string|array $header): array` + +`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist) +which performs the same operation with a cleaned up API and improved +documentation. + +Converts an array of header values that may contain comma separated +headers into an array of headers with no comma separated values. + + +## `GuzzleHttp\Psr7\Query::parse` + +`public static function parse(string $str, int|bool $urlEncoding = true): array` + +Parse a query string into an associative array. + +If multiple values are found for the same key, the value of that key +value pair will become an array. This function does not parse nested +PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2` +will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`. + + +## `GuzzleHttp\Psr7\Query::build` + +`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986): string` + +Build a query string from an array of key value pairs. + +This function can use the return value of `parse()` to build a query +string. This function does not modify the provided keys when an array is +encountered (like `http_build_query()` would). + + +## `GuzzleHttp\Psr7\Utils::caselessRemove` + +`public static function caselessRemove(iterable $keys, $keys, array $data): array` + +Remove the items given by the keys, case insensitively from the data. + + +## `GuzzleHttp\Psr7\Utils::copyToStream` + +`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void` + +Copy the contents of a stream into another stream until the given number +of bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::copyToString` + +`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string` + +Copy the contents of a stream into a string until the given number of +bytes have been read. + + +## `GuzzleHttp\Psr7\Utils::hash` + +`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string` + +Calculate a hash of a stream. + +This method reads the entire stream to calculate a rolling hash, based on +PHP's `hash_init` functions. + + +## `GuzzleHttp\Psr7\Utils::modifyRequest` + +`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface` + +Clone and modify a request with the given changes. + +This method is useful for reducing the number of clones needed to mutate +a message. + +- method: (string) Changes the HTTP method. +- set_headers: (array) Sets the given headers. +- remove_headers: (array) Remove the given headers. +- body: (mixed) Sets the given body. +- uri: (UriInterface) Set the URI. +- query: (string) Set the query string value of the URI. +- version: (string) Set the protocol version. + + +## `GuzzleHttp\Psr7\Utils::readLine` + +`public static function readLine(StreamInterface $stream, int $maxLength = null): string` + +Read a line from the stream up to the maximum allowed buffer length. + + +## `GuzzleHttp\Psr7\Utils::streamFor` + +`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface` + +Create a new stream based on the input type. + +Options is an associative array that can contain the following keys: + +- metadata: Array of custom metadata. +- size: Size of the stream. + +This method accepts the following `$resource` types: + +- `Psr\Http\Message\StreamInterface`: Returns the value as-is. +- `string`: Creates a stream object that uses the given string as the contents. +- `resource`: Creates a stream object that wraps the given PHP stream resource. +- `Iterator`: If the provided value implements `Iterator`, then a read-only + stream object will be created that wraps the given iterable. Each time the + stream is read from, data from the iterator will fill a buffer and will be + continuously called until the buffer is equal to the requested read size. + Subsequent read calls will first read from the buffer and then call `next` + on the underlying iterator until it is exhausted. +- `object` with `__toString()`: If the object has the `__toString()` method, + the object will be cast to a string and then a stream will be returned that + uses the string value. +- `NULL`: When `null` is passed, an empty stream object is returned. +- `callable` When a callable is passed, a read-only stream object will be + created that invokes the given callable. The callable is invoked with the + number of suggested bytes to read. The callable can return any number of + bytes, but MUST return `false` when there is no more data to return. The + stream object that wraps the callable will invoke the callable until the + number of requested bytes are available. Any additional bytes will be + buffered and used in subsequent reads. + +```php +$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); +$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); + +$generator = function ($bytes) { + for ($i = 0; $i < $bytes; $i++) { + yield ' '; + } +} + +$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100)); +``` + + +## `GuzzleHttp\Psr7\Utils::tryFopen` + +`public static function tryFopen(string $filename, string $mode): resource` + +Safely opens a PHP stream resource using a filename. + +When fopen fails, PHP normally raises a warning. This function adds an +error handler that checks for errors and throws an exception instead. + + +## `GuzzleHttp\Psr7\Utils::tryGetContents` + +`public static function tryGetContents(resource $stream): string` + +Safely gets the contents of a given stream. + +When stream_get_contents fails, PHP normally raises a warning. This +function adds an error handler that checks for errors and throws an +exception instead. + + +## `GuzzleHttp\Psr7\Utils::uriFor` + +`public static function uriFor(string|UriInterface $uri): UriInterface` + +Returns a UriInterface for the given value. + +This function accepts a string or UriInterface and returns a +UriInterface for the given value. If the value is already a +UriInterface, it is returned as-is. + + +## `GuzzleHttp\Psr7\MimeType::fromFilename` + +`public static function fromFilename(string $filename): string|null` + +Determines the mimetype of a file by looking at its extension. + + +## `GuzzleHttp\Psr7\MimeType::fromExtension` + +`public static function fromExtension(string $extension): string|null` + +Maps a file extensions to a mimetype. + + +## Upgrading from Function API + +The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience: + +| Original Function | Replacement Method | +|----------------|----------------| +| `str` | `Message::toString` | +| `uri_for` | `Utils::uriFor` | +| `stream_for` | `Utils::streamFor` | +| `parse_header` | `Header::parse` | +| `normalize_header` | `Header::normalize` | +| `modify_request` | `Utils::modifyRequest` | +| `rewind_body` | `Message::rewindBody` | +| `try_fopen` | `Utils::tryFopen` | +| `copy_to_string` | `Utils::copyToString` | +| `copy_to_stream` | `Utils::copyToStream` | +| `hash` | `Utils::hash` | +| `readline` | `Utils::readLine` | +| `parse_request` | `Message::parseRequest` | +| `parse_response` | `Message::parseResponse` | +| `parse_query` | `Query::parse` | +| `build_query` | `Query::build` | +| `mimetype_from_filename` | `MimeType::fromFilename` | +| `mimetype_from_extension` | `MimeType::fromExtension` | +| `_parse_message` | `Message::parseMessage` | +| `_parse_request_uri` | `Message::parseRequestUri` | +| `get_message_body_summary` | `Message::bodySummary` | +| `_caseless_remove` | `Utils::caselessRemove` | + + +# Additional URI Methods + +Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class, +this library also provides additional functionality when working with URIs as static methods. + +## URI Types + +An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference. +An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, +the base URI. Relative references can be divided into several forms according to +[RFC 3986 Section 4.2](https://tools.ietf.org/html/rfc3986#section-4.2): + +- network-path references, e.g. `//example.com/path` +- absolute-path references, e.g. `/path` +- relative-path references, e.g. `subpath` + +The following methods can be used to identify the type of the URI. + +### `GuzzleHttp\Psr7\Uri::isAbsolute` + +`public static function isAbsolute(UriInterface $uri): bool` + +Whether the URI is absolute, i.e. it has a scheme. + +### `GuzzleHttp\Psr7\Uri::isNetworkPathReference` + +`public static function isNetworkPathReference(UriInterface $uri): bool` + +Whether the URI is a network-path reference. A relative reference that begins with two slash characters is +termed an network-path reference. + +### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference` + +`public static function isAbsolutePathReference(UriInterface $uri): bool` + +Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is +termed an absolute-path reference. + +### `GuzzleHttp\Psr7\Uri::isRelativePathReference` + +`public static function isRelativePathReference(UriInterface $uri): bool` + +Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is +termed a relative-path reference. + +### `GuzzleHttp\Psr7\Uri::isSameDocumentReference` + +`public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool` + +Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its +fragment component, identical to the base URI. When no base URI is given, only an empty URI reference +(apart from its fragment) is considered a same-document reference. + +## URI Components + +Additional methods to work with URI components. + +### `GuzzleHttp\Psr7\Uri::isDefaultPort` + +`public static function isDefaultPort(UriInterface $uri): bool` + +Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null +or the standard port. This method can be used independently of the implementation. + +### `GuzzleHttp\Psr7\Uri::composeComponents` + +`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string` + +Composes a URI reference string from its various components according to +[RFC 3986 Section 5.3](https://tools.ietf.org/html/rfc3986#section-5.3). Usually this method does not need to be called +manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`. + +### `GuzzleHttp\Psr7\Uri::fromParts` + +`public static function fromParts(array $parts): UriInterface` + +Creates a URI from a hash of [`parse_url`](https://www.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. + +## Cross-Origin Detection + +`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URL should be considered cross-origin. + +### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin` + +`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool` + +Determines if a modified URL should be considered cross-origin with respect to an original URL. + +## 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. + + +## Version Guidance + +| Version | Status | PHP Version | +|---------|----------------|------------------| +| 1.x | Security fixes | >=5.4,<8.1 | +| 2.x | Latest | ^7.2.5 \|\| ^8.0 | + + +## Security + +If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information. + + +## License + +Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. + + +## For Enterprise + +Available as part of the Tidelift Subscription + +The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json new file mode 100644 index 0000000..cd91040 --- /dev/null +++ b/vendor/guzzlehttp/psr7/composer.json @@ -0,0 +1,96 @@ +{ + "name": "guzzlehttp/psr7", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "request", + "response", + "message", + "stream", + "http", + "uri", + "url", + "psr-7" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Psr7\\": "tests/" + } + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000..cbcfaee --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,248 @@ +addStream($stream); + } + } + + public function __toString(): string + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + 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): void + { + 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(): string + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + */ + public function close(): void + { + $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. + */ + public function detach() + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->detach(); + } + + $this->streams = []; + + return null; + } + + public function tell(): int + { + 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. + */ + public function getSize(): ?int + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof(): bool + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind(): void + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + */ + public function seek($offset, $whence = SEEK_SET): void + { + 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. + */ + public function read($length): string + { + $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); + + if ($result === '') { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return false; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + 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..21be8c0 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,149 @@ +hwm = $hwm; + } + + public function __toString(): string + { + return $this->getContents(); + } + + public function getContents(): string + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close(): void + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + + return null; + } + + public function getSize(): ?int + { + return strlen($this->buffer); + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof(): bool + { + return strlen($this->buffer) === 0; + } + + public function tell(): int + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length): string + { + $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): int + { + $this->buffer .= $string; + + if (strlen($this->buffer) >= $this->hwm) { + return 0; + } + + return strlen($string); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + 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..f34722c --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,153 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); + } + + public function getSize(): ?int + { + $remoteSize = $this->remoteStream->getSize(); + + if (null === $remoteSize) { + return null; + } + + return max($this->stream->getSize(), $remoteSize); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + 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): string + { + // 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): int + { + // 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(): bool + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close(): void + { + $this->remoteStream->close(); + $this->stream->close(); + } + + private function cacheEntireStream(): int + { + $target = new FnStream(['write' => 'strlen']); + Utils::copyToStream($this, $target); + + return $this->tell(); + } +} diff --git a/vendor/guzzlehttp/psr7/src/DroppingStream.php b/vendor/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 0000000..6e3d209 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,49 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string): int + { + $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/Exception/MalformedUriException.php b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php new file mode 100644 index 0000000..3a08477 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php @@ -0,0 +1,14 @@ + */ + private $methods; + + /** + * @param array $methods Hash of method name to a callable. + */ + public function __construct(array $methods) + { + $this->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(string $name): void + { + 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(): void + { + 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) { + /** @var callable $callable */ + $callable = [$stream, $diff]; + $methods[$diff] = $callable; + } + + return new self($methods); + } + + public function __toString(): string + { + try { + return call_user_func($this->_fn___toString); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function close(): void + { + call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize(): ?int + { + return call_user_func($this->_fn_getSize); + } + + public function tell(): int + { + return call_user_func($this->_fn_tell); + } + + public function eof(): bool + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable(): bool + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind(): void + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET): void + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable(): bool + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string): int + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable(): bool + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length): string + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents(): string + { + return call_user_func($this->_fn_getContents); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/vendor/guzzlehttp/psr7/src/Header.php b/vendor/guzzlehttp/psr7/src/Header.php new file mode 100644 index 0000000..4d7005b --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Header.php @@ -0,0 +1,134 @@ +]+>|[^=]+/', $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. + * + * @deprecated Use self::splitList() instead. + */ + public static function normalize($header): array + { + $result = []; + foreach ((array) $header as $value) { + foreach (self::splitList($value) as $parsed) { + $result[] = $parsed; + } + } + + return $result; + } + + /** + * Splits a HTTP header defined to contain a comma-separated list into + * each individual value. Empty values will be removed. + * + * Example headers include 'accept', 'cache-control' and 'if-none-match'. + * + * This method must not be used to parse headers that are not defined as + * a list, such as 'user-agent' or 'set-cookie'. + * + * @param string|string[] $values Header value as returned by MessageInterface::getHeader() + * + * @return string[] + */ + public static function splitList($values): array + { + if (!\is_array($values)) { + $values = [$values]; + } + + $result = []; + foreach ($values as $value) { + if (!\is_string($value)) { + throw new \TypeError('$header must either be a string or an array containing strings.'); + } + + $v = ''; + $isQuoted = false; + $isEscaped = false; + for ($i = 0, $max = \strlen($value); $i < $max; $i++) { + if ($isEscaped) { + $v .= $value[$i]; + $isEscaped = false; + + continue; + } + + if (!$isQuoted && $value[$i] === ',') { + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + + $v = ''; + continue; + } + + if ($isQuoted && $value[$i] === '\\') { + $isEscaped = true; + $v .= $value[$i]; + + continue; + } + if ($value[$i] === '"') { + $isQuoted = !$isQuoted; + $v .= $value[$i]; + + continue; + } + + $v .= $value[$i]; + } + + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + } + + return $result; + } +} diff --git a/vendor/guzzlehttp/psr7/src/HttpFactory.php b/vendor/guzzlehttp/psr7/src/HttpFactory.php new file mode 100644 index 0000000..30be222 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/HttpFactory.php @@ -0,0 +1,100 @@ +getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + + public function createStream(string $content = ''): StreamInterface + { + return Utils::streamFor($content); + } + + public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface + { + try { + $resource = Utils::tryFopen($file, $mode); + } catch (\RuntimeException $e) { + if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) { + throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e); + } + + throw $e; + } + + return Utils::streamFor($resource); + } + + public function createStreamFromResource($resource): StreamInterface + { + return Utils::streamFor($resource); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + if (empty($method)) { + if (!empty($serverParams['REQUEST_METHOD'])) { + $method = $serverParams['REQUEST_METHOD']; + } else { + throw new \InvalidArgumentException('Cannot determine HTTP method'); + } + } + + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + return new Response($code, [], null, '1.1', $reasonPhrase); + } + + public function createRequest(string $method, $uri): RequestInterface + { + return new Request($method, $uri); + } + + public function createUri(string $uri = ''): UriInterface + { + return new Uri($uri); + } +} diff --git a/vendor/guzzlehttp/psr7/src/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000..8e00f1c --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,37 @@ + 15 + 32]); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000..5618331 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,41 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + */ + protected function createStream(): StreamInterface + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/vendor/guzzlehttp/psr7/src/LimitStream.php b/vendor/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 0000000..fb22325 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,157 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof(): bool + { + // 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 + */ + public function getSize(): ?int + { + 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 + */ + public function seek($offset, $whence = SEEK_SET): void + { + 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() + */ + public function tell(): int + { + 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(int $offset): void + { + $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(int $limit): void + { + $this->limit = $limit; + } + + public function read($length): string + { + if ($this->limit === -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Message.php b/vendor/guzzlehttp/psr7/src/Message.php new file mode 100644 index 0000000..61c1a5d --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Message.php @@ -0,0 +1,246 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + if (strtolower($name) === 'set-cookie') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: " . $value; + } + } else { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + } + + return "{$msg}\r\n\r\n" . $message->getBody(); + } + + /** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + */ + public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string + { + $body = $message->getBody(); + + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $body->rewind(); + $summary = $body->read($truncateAt); + $body->rewind(); + + if ($size > $truncateAt) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) { + return null; + } + + return $summary; + } + + /** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` + * returns a value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ + public static function rewindBody(MessageInterface $message): void + { + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } + } + + /** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + */ + public static function parseMessage(string $message): array + { + 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'); + } + + [$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'); + } + + [$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). + */ + public static function parseRequestUri(string $path, array $headers): string + { + $hostKey = array_filter(array_keys($headers), function ($k) { + // Numeric array keys are converted to int by PHP. + $k = (string) $k; + + return strtolower($k) === 'host'; + }); + + // If no host is found, then a full URI cannot be constructed. + if (!$hostKey) { + return $path; + } + + $host = $headers[reset($hostKey)][0]; + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme . '://' . $host . '/' . ltrim($path, '/'); + } + + /** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + */ + public static function parseRequest(string $message): RequestInterface + { + $data = self::parseMessage($message); + $matches = []; + if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1'; + + $request = new Request( + $parts[0], + $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], + $data['headers'], + $data['body'], + $version + ); + + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + } + + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + */ + public static function parseResponse(string $message): ResponseInterface + { + $data = self::parseMessage($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); + } + $parts = explode(' ', $data['start-line'], 3); + + return new Response( + (int) $parts[1], + $data['headers'], + $data['body'], + explode('/', $parts[0])[1], + $parts[2] ?? null + ); + } +} diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000..d2dc28b --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,264 @@ + Map of all registered headers, as original name => array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion(): string + { + return $this->protocol; + } + + public function withProtocolVersion($version): MessageInterface + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($header): bool + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header): array + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header): string + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value): MessageInterface + { + $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): MessageInterface + { + $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): MessageInterface + { + $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(): StreamInterface + { + if (!$this->stream) { + $this->stream = Utils::streamFor(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body): MessageInterface + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + /** + * @param array $headers + */ + private function setHeaders(array $headers): void + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + // Numeric array keys are converted to int by PHP. + $header = (string) $header; + + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * @param mixed $value + * + * @return string[] + */ + private function normalizeHeaderValue($value): array + { + if (!is_array($value)) { + return $this->trimAndValidateHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimAndValidateHeaderValues($value); + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param mixed[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimAndValidateHeaderValues(array $values): array + { + return array_map(function ($value) { + if (!is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(sprintf( + 'Header value must be scalar or null but %s provided.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + $trimmed = trim((string) $value, " \t"); + $this->assertValue($trimmed); + + return $trimmed; + }, array_values($values)); + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * @param mixed $header + */ + private function assertHeader($header): void + { + 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 (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $header)) { + throw new \InvalidArgumentException( + sprintf( + '"%s" is not valid header name', + $header + ) + ); + } + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + * VCHAR = %x21-7E + * obs-text = %x80-FF + * obs-fold = CRLF 1*( SP / HTAB ) + */ + private function assertValue(string $value): void + { + // The regular expression intentionally does not support the obs-fold production, because as + // per RFC 7230#3.2.4: + // + // A sender MUST NOT generate a message that includes + // line folding (i.e., that has any field-value that contains a match to + // the obs-fold rule) unless the message is intended for packaging + // within the message/http media type. + // + // Clients must not send a request with line folding and a server sending folded headers is + // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting + // folding is not likely to break any legitimate use case. + if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/', $value)) { + throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value)); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/MimeType.php b/vendor/guzzlehttp/psr7/src/MimeType.php new file mode 100644 index 0000000..0debbd1 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MimeType.php @@ -0,0 +1,1237 @@ + 'application/vnd.1000minds.decision-model+xml', + '3dml' => 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + '3gpp' => 'video/3gpp', + '3mf' => 'model/3mf', + '7z' => 'application/x-7z-compressed', + '7zip' => 'application/x-7z-compressed', + '123' => 'application/vnd.lotus-1-2-3', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-acc', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac3' => 'audio/ac3', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'age' => 'application/vnd.age', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/pdf', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'amr' => 'audio/amr', + 'apk' => 'application/vnd.android.package-archive', + 'apng' => 'image/apng', + 'appcache' => 'text/cache-manifest', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'arj' => 'application/x-arj', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomdeleted' => 'application/atomdeleted+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/x-au', + 'avci' => 'image/avci', + 'avcs' => 'image/avcs', + 'avi' => 'video/x-msvideo', + 'avif' => 'image/avif', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azv' => 'image/vnd.airzip.accelerator.azv', + 'azw' => 'application/vnd.amazon.ebook', + 'b16' => 'image/vnd.pco.b16', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bdoc' => 'application/x-bdoc', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmml' => 'application/vnd.balsamiq.bmml+xml', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btif' => 'image/prs.btif', + 'buffer' => 'application/octet-stream', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cco' => 'application/x-cocoa', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdfx' => 'application/cdfx+xml', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdr' => 'application/cdr', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cjs' => 'application/node', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/octet-stream', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'coffee' => 'text/coffeescript', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpl' => 'application/cpl+xml', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'crx' => 'application/x-chrome-extension', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csl' => 'application/vnd.citationstyles.style+xml', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'csr' => 'application/octet-stream', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbf' => 'application/vnd.dbf', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'ddf' => 'application/vnd.syncml.dmddf+xml', + 'dds' => 'image/vnd.ms-dds', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'disposition-notification' => 'message/disposition-notification', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/x-apple-diskimage', + 'dmn' => 'application/octet-stream', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'drle' => 'image/dicom-rle', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwd' => 'application/atsc-dwd+xml', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ear' => 'application/java-archive', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'image/emf', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emotionml' => 'application/emotionml+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es' => 'application/ecmascript', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'exi' => 'application/exi', + 'exp' => 'application/express', + 'exr' => 'image/aces', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/mp4', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fdt' => 'application/fdt+xml', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fits' => 'image/fits', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'fo' => 'application/vnd.software602.filler.form+xml', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'gdoc' => 'application/vnd.google-apps.document', + 'ged' => 'text/vnd.familysearch.gedcom', + 'geo' => 'application/vnd.dynageo', + 'geojson' => 'application/geo+json', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'glb' => 'model/gltf-binary', + 'gltf' => 'model/gltf+json', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gpg' => 'application/gpg-keys', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gslides' => 'application/vnd.google-apps.presentation', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/gzip', + 'gzip' => 'application/gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hbs' => 'text/x-handlebars-template', + 'hdd' => 'application/x-virtualbox-hdd', + 'hdf' => 'application/x-hdf', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'hej2' => 'image/hej2k', + 'held' => 'application/atsc-held+xml', + 'hh' => 'text/x-c', + 'hjson' => 'application/hjson', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hsj2' => 'image/hsj2', + 'htc' => 'text/x-component', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'img' => 'application/octet-stream', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'its' => 'application/its+xml', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jade' => 'text/jade', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'jardiff' => 'application/x-java-archive-diff', + 'java' => 'text/x-java-source', + 'jhc' => 'image/jphc', + 'jisp' => 'application/vnd.jisp', + 'jls' => 'image/jls', + 'jlt' => 'application/vnd.hp-jlyt', + 'jng' => 'image/x-jng', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpf' => 'image/jpx', + 'jpg' => 'image/jpeg', + 'jpg2' => 'image/jp2', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jph' => 'image/jph', + 'jpm' => 'video/jpm', + 'jpx' => 'image/jpx', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'json5' => 'application/json5', + 'jsonld' => 'application/ld+json', + 'jsonml' => 'application/jsonml+json', + 'jsx' => 'text/jsx', + 'jxr' => 'image/jxr', + 'jxra' => 'image/jxra', + 'jxrs' => 'image/jxrs', + 'jxs' => 'image/jxs', + 'jxsc' => 'image/jxsc', + 'jxsi' => 'image/jxsi', + 'jxss' => 'image/jxss', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kdb' => 'application/octet-stream', + 'kdbx' => 'application/x-keepass2', + 'key' => 'application/x-iwork-keynote-sffkey', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktx2' => 'image/ktx2', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'less' => 'text/less', + 'lgr' => 'application/lgr+xml', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'litcoffee' => 'text/coffeescript', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lua' => 'text/x-lua', + 'luac' => 'application/x-lua-bytecode', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'text/plain', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/x-m4a', + 'm4p' => 'application/mp4', + 'm4s' => 'video/iso.segment', + 'm4u' => 'application/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm21' => 'application/mp21', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'maei' => 'application/mmt-aei+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'manifest' => 'text/cache-manifest', + 'map' => 'application/json', + 'mar' => 'application/octet-stream', + 'markdown' => 'text/markdown', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'md' => 'text/markdown', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'mdx' => 'text/mdx', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mjs' => 'application/javascript', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mkd' => 'text/x-markdown', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mml' => 'text/mathml', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mp21' => 'application/mp21', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpd' => 'application/dash+xml', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpf' => 'application/media-policy-dataset+xml', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msg' => 'application/vnd.ms-outlook', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msm' => 'application/octet-stream', + 'msp' => 'application/octet-stream', + 'msty' => 'application/vnd.muvee.style', + 'mtl' => 'model/mtl', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musd' => 'application/mmt-usd+xml', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mvt' => 'application/vnd.mapbox-vector-tile', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxmf' => 'audio/mobile-xmf', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nq' => 'application/n-quads', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'nt' => 'application/n-triples', + 'ntf' => 'application/vnd.nitf', + 'numbers' => 'application/x-iwork-numbers-sffnumbers', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obgx' => 'application/vnd.openblox.game+xml', + 'obj' => 'model/obj', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogex' => 'model/vnd.opengex', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'opus' => 'audio/ogg', + 'org' => 'text/x-org', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'osm' => 'application/vnd.openstreetmap.data+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'font/otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ova' => 'application/x-virtualbox-ova', + 'ovf' => 'application/x-virtualbox-ovf', + 'owl' => 'application/rdf+xml', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p7a' => 'application/x-pkcs7-signature', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'pages' => 'application/x-iwork-pages-sffpages', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/x-pilot', + 'pde' => 'text/x-processing', + 'pdf' => 'application/pdf', + 'pem' => 'application/x-x509-user-cert', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp', + 'phar' => 'application/octet-stream', + 'php' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'phtml' => 'application/x-httpd-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'pkpass' => 'application/vnd.apple.pkpass', + 'pl' => 'application/x-perl', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pm' => 'application/x-perl', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'model/prc', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'provx' => 'application/provenance+xml', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'application/x-photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'pti' => 'image/prs.pti', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'raml' => 'application/raml+yaml', + 'rapd' => 'application/route-apd+xml', + 'rar' => 'application/x-rar', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'relo' => 'application/p2p-overlay+xml', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rng' => 'application/xml', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsa' => 'application/x-pkcs7', + 'rsat' => 'application/atsc-rsat+xml', + 'rsd' => 'application/rsd+xml', + 'rsheet' => 'application/urc-ressheet+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'run' => 'application/x-makeself', + 'rusd' => 'application/route-usd+xml', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sass' => 'text/x-sass', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scss' => 'text/x-scss', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'sea' => 'application/octet-stream', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'senmlx' => 'application/senml+xml', + 'sensmlx' => 'application/sensml+xml', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shex' => 'text/shex', + 'shf' => 'application/shf+xml', + 'shtml' => 'text/html', + 'sid' => 'image/x-mrsid-image', + 'sieve' => 'application/sieve', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'siv' => 'application/sieve', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slim' => 'text/slim', + 'slm' => 'text/slim', + 'sls' => 'application/route-s-tsid+xml', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spdx' => 'text/spdx', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'sst' => 'application/octet-stream', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'model/stl', + 'stpx' => 'model/step+xml', + 'stpxz' => 'model/step-xml+zip', + 'stpz' => 'model/step+zip', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'styl' => 'text/stylus', + 'stylus' => 'text/stylus', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'swidtag' => 'application/swid+xml', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 't38' => 'image/t38', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tap' => 'image/vnd.tencent.tap', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'td' => 'application/urc-targetdesc+xml', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tfx' => 'image/tiff-fx', + 'tga' => 'image/x-tga', + 'tgz' => 'application/x-tar', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tk' => 'application/x-tcl', + 'tmo' => 'application/vnd.tmobile-livetv', + 'toml' => 'application/toml', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trig' => 'application/trig', + 'trm' => 'application/x-msterminal', + 'ts' => 'video/mp2t', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'font/collection', + 'ttf' => 'font/ttf', + 'ttl' => 'text/turtle', + 'ttml' => 'application/ttml+xml', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u3d' => 'model/u3d', + 'u8dsn' => 'message/global-delivery-status', + 'u8hdr' => 'message/global-headers', + 'u8mdn' => 'message/global-disposition-notification', + 'u8msg' => 'message/global', + 'u32' => 'application/x-authorware-bin', + 'ubj' => 'application/ubjson', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'usdz' => 'model/vnd.usdz+zip', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vbox' => 'application/x-virtualbox-vbox', + 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vdi' => 'application/x-virtualbox-vdi', + 'vds' => 'model/vnd.sap.vds', + 'vhd' => 'application/x-virtualbox-vhd', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vlc' => 'application/videolan', + 'vmdk' => 'application/x-virtualbox-vmdk', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtf' => 'image/vnd.valve.source.texture', + 'vtt' => 'text/vtt', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wadl' => 'application/vnd.sun.wadl+xml', + 'war' => 'application/java-archive', + 'wasm' => 'application/wasm', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webapp' => 'application/x-web-app-manifest+json', + 'webm' => 'video/webm', + 'webmanifest' => 'application/manifest+json', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wif' => 'application/watcherinfo+xml', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'image/wmf', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + 'word' => 'application/msword', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsc' => 'message/vnd.wfa.wsc', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+fastinfoset', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d-vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'x32' => 'application/x-authorware-bin', + 'x_b' => 'model/vnd.parasolid.transmit.binary', + 'x_t' => 'model/vnd.parasolid.transmit.text', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xav' => 'application/xcap-att+xml', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xca' => 'application/xcap-caps+xml', + 'xcs' => 'application/calendar+xml', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xel' => 'application/xcap-el+xml', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xns' => 'application/xcap-ns+xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsd' => 'application/xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'ymp' => 'text/x-suse-ymp', + 'z' => 'application/x-compress', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'zsh' => 'text/x-scriptzsh', + ]; + + /** + * Determines the mimetype of a file by looking at its extension. + * + * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromFilename(string $filename): ?string + { + return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } + + /** + * Maps a file extensions to a mimetype. + * + * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromExtension(string $extension): ?string + { + return self::MIME_TYPES[strtolower($extension)] ?? null; + } +} diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000..3e12b74 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,159 @@ +boundary = $boundary ?: bin2hex(random_bytes(20)); + $this->stream = $this->createStream($elements); + } + + public function getBoundary(): string + { + return $this->boundary; + } + + public function isWritable(): bool + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + * + * @param array $headers + */ + private function getHeaders(array $headers): string + { + $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 = []): StreamInterface + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + if (!is_array($element)) { + throw new \UnexpectedValueException("An array is expected"); + } + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element): void + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = Utils::streamFor($element['contents']); + + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') { + $element['filename'] = $uri; + } + } + + [$body, $headers] = $this->createElement( + $element['name'], + $element['contents'], + $element['filename'] ?? null, + $element['headers'] ?? [] + ); + + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(Utils::streamFor("\r\n")); + } + + private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf( + 'form-data; name="%s"; filename="%s"', + $name, + basename($filename) + ) + : "form-data; name=\"{$name}\""; + } + + // Set a default content-length header if one was no provided + $length = $this->getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = MimeType::fromFilename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, string $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..161a224 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,28 @@ +source = $source; + $this->size = $options['size'] ?? null; + $this->metadata = $options['metadata'] ?? []; + $this->buffer = new BufferStream(); + } + + public function __toString(): string + { + try { + return Utils::copyToString($this); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function close(): void + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = 0; + $this->source = null; + + return null; + } + + public function getSize(): ?int + { + return $this->size; + } + + public function tell(): int + { + return $this->tellPos; + } + + public function eof(): bool + { + return $this->source === null; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable(): bool + { + return false; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable(): bool + { + return true; + } + + public function read($length): string + { + $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(): string + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return $this->metadata[$key] ?? null; + } + + private function pump(int $length): void + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/Query.php b/vendor/guzzlehttp/psr7/src/Query.php new file mode 100644 index 0000000..2faab3a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Query.php @@ -0,0 +1,113 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + */ + public static function parse(string $str, $urlEncoding = true): array + { + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', (string) $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 (!array_key_exists($key, $result)) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; + } + + /** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986 + * to encode using RFC3986, or PHP_QUERY_RFC1738 + * to encode using RFC1738. + */ + public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string + { + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function (string $str): string { + 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((string) $k); + if (!is_array($v)) { + $qs .= $k; + $v = is_bool($v) ? (int) $v : $v; + if ($v !== null) { + $qs .= '=' . $encoder((string) $v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + $vv = is_bool($vv) ? (int) $vv : $vv; + if ($vv !== null) { + $qs .= '=' . $encoder((string) $vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; + } +} diff --git a/vendor/guzzlehttp/psr7/src/Request.php b/vendor/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000..b17af66 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,157 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1' + ) { + $this->assertMethod($method); + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!isset($this->headerNames['host'])) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + } + + public function getRequestTarget(): string + { + 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): RequestInterface + { + 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(): string + { + return $this->method; + } + + public function withMethod($method): RequestInterface + { + $this->assertMethod($method); + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri(): UriInterface + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface + { + 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(): void + { + $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; + } + + /** + * @param mixed $method + */ + private function assertMethod($method): void + { + 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..4c6ee6f --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,160 @@ + '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', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request 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', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct( + int $status = 200, + array $headers = [], + $body = null, + string $version = '1.1', + string $reason = null + ) { + $this->assertStatusCodeRange($status); + + $this->statusCode = $status; + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::PHRASES[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = ''): ResponseInterface + { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + + $new = clone $this; + $new->statusCode = $code; + if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $new->reasonPhrase = (string) $reasonPhrase; + return $new; + } + + /** + * @param mixed $statusCode + */ + private function assertStatusCodeIsInteger($statusCode): void + { + if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + + private function assertStatusCodeRange(int $statusCode): void + { + 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..3022401 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php @@ -0,0 +1,23 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + public 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..43cbb50 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,344 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + * @param array $serverParams Typically the $_SERVER superglobal + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1', + array $serverParams = [] + ) { + $this->serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files An array which respect $_FILES structure + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalizeFiles(array $files): array + { + $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 UploadedFileInterface|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. + * + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []): array + { + $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 + */ + public static function fromGlobals(): ServerRequestInterface + { + $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(string $authority): array + { + $uri = 'http://' . $authority; + $parts = parse_url($uri); + if (false === $parts) { + return [null, null]; + } + + $host = $parts['host'] ?? null; + $port = $parts['port'] ?? null; + + return [$host, $port]; + } + + /** + * Get a Uri populated with values from $_SERVER. + */ + public static function getUriFromGlobals(): UriInterface + { + $uri = new Uri(''); + + $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); + + $hasPort = false; + if (isset($_SERVER['HTTP_HOST'])) { + [$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; + } + + public function getServerParams(): array + { + return $this->serverParams; + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + public function getCookieParams(): array + { + return $this->cookieParams; + } + + public function withCookieParams(array $cookies): ServerRequestInterface + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + public function getQueryParams(): array + { + return $this->queryParams; + } + + public function withQueryParams(array $query): ServerRequestInterface + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + * + * @return array|object|null + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + public function withParsedBody($data): ServerRequestInterface + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + public function withAttribute($attribute, $value): ServerRequestInterface + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + public function withoutAttribute($attribute): ServerRequestInterface + { + 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..ecd3186 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,282 @@ +size = $options['size']; + } + + $this->customMetadata = $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(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + return Utils::tryGetContents($this->stream); + } + + public function close(): void + { + 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(): ?int + { + 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 (is_array($stats) && isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function eof(): bool + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + return feof($this->stream); + } + + public function tell(): int + { + 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(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $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): string + { + 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 ''; + } + + try { + $string = fread($this->stream, $length); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to read from stream', 0, $e); + } + + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string): int + { + 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; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + 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 $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..56d4104 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,155 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @return StreamInterface + */ + public function __get(string $name) + { + if ($name === 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString(): string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR); + return ''; + } + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @return mixed + */ + public function __call(string $method, array $args) + { + /** @var callable $callable */ + $callable = [$this->stream, $method]; + $result = call_user_func_array($callable, $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close(): void + { + $this->stream->close(); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize(): ?int + { + return $this->stream->getSize(); + } + + public function eof(): bool + { + return $this->stream->eof(); + } + + public function tell(): int + { + return $this->stream->tell(); + } + + public function isReadable(): bool + { + return $this->stream->isReadable(); + } + + public function isWritable(): bool + { + return $this->stream->isWritable(); + } + + public function isSeekable(): bool + { + return $this->stream->isSeekable(); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $this->stream->seek($offset, $whence); + } + + public function read($length): string + { + return $this->stream->read($length); + } + + public function write($string): int + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @throws \BadMethodCallException + */ + protected function createStream(): StreamInterface + { + 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..2a93464 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,175 @@ +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, false, self::createStreamContext($stream)); + } + + /** + * Creates a stream context that can be used to open a stream as a php stream resource. + * + * @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(): void + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool + { + $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(int $count): string + { + return $this->stream->read($count); + } + + public function stream_write(string $data): int + { + return $this->stream->write($data); + } + + public function stream_tell(): int + { + return $this->stream->tell(); + } + + public function stream_eof(): bool + { + return $this->stream->eof(); + } + + public function stream_seek(int $offset, int $whence): bool + { + $this->stream->seek($offset, $whence); + + return true; + } + + /** + * @return resource|false + */ + public function stream_cast(int $cast_as) + { + $stream = clone($this->stream); + $resource = $stream->detach(); + + return $resource ?? false; + } + + /** + * @return array + */ + public function stream_stat(): array + { + 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 + ]; + } + + /** + * @return array + */ + public function url_stat(string $path, int $flags): array + { + 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..b1521bc --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,211 @@ +setError($errorStatus); + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param StreamInterface|string|resource $streamOrFile + * + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile): void + { + 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' + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function setError(int $error): void + { + if (false === in_array($error, UploadedFile::ERRORS, true)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + private function isStringNotEmpty($param): bool + { + return is_string($param) && false === empty($param); + } + + /** + * Return true if there is no upload error + */ + private function isOk(): bool + { + return $this->error === UPLOAD_ERR_OK; + } + + public function isMoved(): bool + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + 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'); + } + } + + public function getStream(): StreamInterface + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + /** @var string $file */ + $file = $this->file; + + return new LazyOpenStream($file, 'r+'); + } + + public function moveTo($targetPath): void + { + $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 === 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + Utils::copyToStream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + public function getSize(): ?int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFilename; + } + + public function getClientMediaType(): ?string + { + 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..09e878d --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,740 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + /** + * Unreserved characters for use in a regex. + * + * @link https://tools.ietf.org/html/rfc3986#section-2.3 + */ + private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; + + /** + * Sub-delims for use in a regex. + * + * @link https://tools.ietf.org/html/rfc3986#section-2.2 + */ + private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; + private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%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 = ''; + + /** @var string|null String representation */ + private $composedComponents; + + public function __construct(string $uri = '') + { + if ($uri !== '') { + $parts = self::parse($uri); + if ($parts === false) { + throw new MalformedUriException("Unable to parse URI: $uri"); + } + $this->applyParts($parts); + } + } + /** + * UTF-8 aware \parse_url() replacement. + * + * The internal function produces broken output for non ASCII domain names + * (IDN) when used with locales other than "C". + * + * On the other hand, cURL understands IDN correctly only when UTF-8 locale + * is configured ("C.UTF-8", "en_US.UTF-8", etc.). + * + * @see https://bugs.php.net/bug.php?id=52923 + * @see https://www.php.net/manual/en/function.parse-url.php#114817 + * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING + * + * @return array|false + */ + private static function parse(string $url) + { + // If IPv6 + $prefix = ''; + if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { + /** @var array{0:string, 1:string, 2:string} $matches */ + $prefix = $matches[1]; + $url = $matches[2]; + } + + /** @var string */ + $encodedUrl = preg_replace_callback( + '%[^:/@?&=#]+%usD', + static function ($matches) { + return urlencode($matches[0]); + }, + $url + ); + + $result = parse_url($prefix . $encodedUrl); + + if ($result === false) { + return false; + } + + return array_map('urldecode', $result); + } + + public function __toString(): string + { + if ($this->composedComponents === null) { + $this->composedComponents = self::composeComponents( + $this->scheme, + $this->getAuthority(), + $this->path, + $this->query, + $this->fragment + ); + } + + return $this->composedComponents; + } + + /** + * 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). + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string + { + $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; + } + + if ($authority != '' && $path != '' && $path[0] != '/') { + $path = '/' . $path; + } + + $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. + */ + public static function isDefaultPort(UriInterface $uri): bool + { + return $uri->getPort() === null + || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$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' + * + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri): bool + { + 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. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri): bool + { + 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. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri): bool + { + 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. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri): bool + { + 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 + * + * @link https://tools.ietf.org/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool + { + 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() === ''; + } + + /** + * 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. + */ + public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface + { + $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 + */ + public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface + { + $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 + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface + { + $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); + + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null); + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @link http://php.net/manual/en/function.parse-url.php + * + * @throws MalformedUriException If the components do not form a valid URI. + */ + public static function fromParts(array $parts): UriInterface + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + return $this->path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + public function withScheme($scheme): UriInterface + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null): UriInterface + { + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withHost($host): UriInterface + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withPort($port): UriInterface + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path): UriInterface + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withQuery($query): UriInterface + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + $new->composedComponents = null; + + return $new; + } + + public function withFragment($fragment): UriInterface + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + $new->composedComponents = null; + + return $new; + } + + public function jsonSerialize(): string + { + return $this->__toString(); + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts): void + { + $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 mixed $scheme + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme): string + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $component + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component): string + { + if (!is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + + return preg_replace_callback( + '/(?:[^%' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $component + ); + } + + /** + * @param mixed $host + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host): string + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $port + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port): ?int + { + 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 string[] $keys + * + * @return string[] + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys): array + { + $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); + }); + } + + private static function generateQueryString(string $key, ?string $value): string + { + // 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::QUERY_SEPARATORS_REPLACEMENT); + + if ($value !== null) { + $queryString .= '=' . strtr($value, self::QUERY_SEPARATORS_REPLACEMENT); + } + + return $queryString; + } + + private function removeDefaultPort(): void + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param mixed $path + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path): string + { + if (!is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $path + ); + } + + /** + * Filters the query string or fragment of a URI. + * + * @param mixed $str + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str): string + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match): string + { + return rawurlencode($match[0]); + } + + private function validateState(): void + { + 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 MalformedUriException('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 MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriComparator.php b/vendor/guzzlehttp/psr7/src/UriComparator.php new file mode 100644 index 0000000..70c582a --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriComparator.php @@ -0,0 +1,52 @@ +getHost(), $modified->getHost()) !== 0) { + return true; + } + + if ($original->getScheme() !== $modified->getScheme()) { + return true; + } + + if (self::computePort($original) !== self::computePort($modified)) { + return true; + } + + return false; + } + + private static function computePort(UriInterface $uri): int + { + $port = $uri->getPort(); + + if (null !== $port) { + return $port; + } + + return 'https' === $uri->getScheme() ? 443 : 80; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/vendor/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 0000000..e12971e --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,220 @@ +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 + * + * @link https://tools.ietf.org/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri): UriInterface + { + $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): UriInterface + { + $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..426e5c9 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,211 @@ +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 + */ + public static function relativize(UriInterface $base, UriInterface $target): UriInterface + { + 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()); + /** @var string $lastSegment */ + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target): string + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = str_repeat('../', count($sourceSegments)) . implode('/', $targetSegments); + + // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. + if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/vendor/guzzlehttp/psr7/src/Utils.php b/vendor/guzzlehttp/psr7/src/Utils.php new file mode 100644 index 0000000..3a4cf39 --- /dev/null +++ b/vendor/guzzlehttp/psr7/src/Utils.php @@ -0,0 +1,459 @@ + $v) { + if (!is_string($k) || !in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void + { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } + } + + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, int $maxLen = -1): string + { + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + if ($buf === '') { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + if ($buf === '') { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based + * on PHP's `hash_init` functions. + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string + { + $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, $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * Clone and modify a request with the given changes. + * + * This method is useful for reducing the number of clones needed to mutate + * a message. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. + * - remove_headers: (array) Remove the given headers. + * - body: (mixed) Sets the given body. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + */ + public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface + { + if (!$changes) { + return $request; + } + + $headers = $request->getHeaders(); + + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + if ($host = $changes['uri']->getHost()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':' . $port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = self::caselessRemove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + + if ($request instanceof ServerRequestInterface) { + $new = (new ServerRequest( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion(), + $request->getServerParams() + )) + ->withParsedBody($request->getParsedBody()) + ->withQueryParams($request->getQueryParams()) + ->withCookieParams($request->getCookieParams()) + ->withUploadedFiles($request->getUploadedFiles()); + + foreach ($request->getAttributes() as $key => $value) { + $new = $new->withAttribute($key, $value); + } + + return $new; + } + + return new Request( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion() + ); + } + + /** + * Read a line from the stream up to the maximum allowed buffer length. + * + * @param StreamInterface $stream Stream to read from + * @param int|null $maxLength Maximum buffer length + */ + public static function readLine(StreamInterface $stream, ?int $maxLength = null): string + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + if ('' === ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * This method accepts the following `$resource` types: + * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. + * - `string`: Creates a stream object that uses the given string as the contents. + * - `resource`: Creates a stream object that wraps the given PHP stream resource. + * - `Iterator`: If the provided value implements `Iterator`, then a read-only + * stream object will be created that wraps the given iterable. Each time the + * stream is read from, data from the iterator will fill a buffer and will be + * continuously called until the buffer is equal to the requested read size. + * Subsequent read calls will first read from the buffer and then call `next` + * on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` method, + * the object will be cast to a string and then a stream will be returned that + * uses the string value. + * - `NULL`: When `null` is passed, an empty stream object is returned. + * - `callable` When a callable is passed, a read-only stream object will be + * created that invokes the given callable. The callable is invoked with the + * number of suggested bytes to read. The callable can return any number of + * bytes, but MUST return `false` when there is no more data to return. The + * stream object that wraps the callable will invoke the callable until the + * number of requested bytes are available. Any additional bytes will be + * buffered and used in subsequent reads. + * + * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data + * @param array{size?: int, metadata?: array} $options Additional options + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function streamFor($resource = '', array $options = []): StreamInterface + { + if (is_scalar($resource)) { + $stream = self::tryFopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, (string) $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + /* + * The 'php://input' is a special stream with quirks and inconsistencies. + * We avoid using that stream by reading it into php://temp + */ + + /** @var resource $resource */ + if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') { + $stream = self::tryFopen('php://temp', 'w+'); + stream_copy_to_stream($resource, $stream); + fseek($stream, 0); + $resource = $stream; + } + return new Stream($resource, $options); + case 'object': + /** @var object $resource */ + 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 self::streamFor((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(self::tryFopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource)); + } + + /** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * + * @throws \RuntimeException if the file cannot be opened + */ + public static function tryFopen(string $filename, string $mode) + { + $ex = null; + set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $errstr + )); + + return true; + }); + + try { + /** @var resource $handle */ + $handle = fopen($filename, $mode); + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to open "%s" using mode "%s": %s', + $filename, + $mode, + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; + } + + /** + * Safely gets the contents of a given stream. + * + * When stream_get_contents fails, PHP normally raises a warning. This + * function adds an error handler that checks for errors and throws an + * exception instead. + * + * @param resource $stream + * + * @throws \RuntimeException if the stream cannot be read + */ + public static function tryGetContents($stream): string + { + $ex = null; + set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool { + $ex = new \RuntimeException(sprintf( + 'Unable to read stream contents: %s', + $errstr + )); + + return true; + }); + + try { + /** @var string|false $contents */ + $contents = stream_get_contents($stream); + + if ($contents === false) { + $ex = new \RuntimeException('Unable to read stream contents'); + } + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to read stream contents: %s', + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $contents; + } + + /** + * Returns a UriInterface for the given value. + * + * This function accepts a string or UriInterface and returns a + * UriInterface for the given value. If the value is already a + * UriInterface, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/vendor/jaeger/g-http/.gitignore b/vendor/jaeger/g-http/.gitignore new file mode 100644 index 0000000..1541c56 --- /dev/null +++ b/vendor/jaeger/g-http/.gitignore @@ -0,0 +1,3 @@ +.idea +/vendor/ +composer.lock \ No newline at end of file diff --git a/vendor/jaeger/g-http/README.md b/vendor/jaeger/g-http/README.md new file mode 100644 index 0000000..f4cd602 --- /dev/null +++ b/vendor/jaeger/g-http/README.md @@ -0,0 +1,158 @@ +# GHttp +基于GuzzleHttp的简单版Http客户端。 Simple Http client base on GuzzleHttp + +## 安装 + +``` +composer require jaeger/g-http +``` + +## 用法 + +#### 1. get / getJson +```php + +use Jaeger\GHttp; + +$rt = GHttp::get('https://www.baidu.com/s?wd=QueryList'); + +$rt = GHttp::get('https://www.baidu.com/s','wd=QueryList&wd2=teststr'); + +//or + +$rt = GHttp::get('https://www.baidu.com/s',[ + 'wd' => 'QueryList', + 'wd2' => 'teststr' +]); + +//opt + +$rt = GHttp::get('https://www.baidu.com/s',[ + 'wd' => 'QueryList' +],[ + 'headers' => [ + 'referer' => 'https://baidu.com', + 'User-Agent' => 'Mozilla/5.0 (Windows NTChrome/58.0.3029.110 Safari/537.36', + 'Cookie' => 'cookie xxx' + ] +]); + +$rt = GHttp::getJson('https://xxxx.com/json'); + +``` + +#### 2.post / postRaw / postJson +```php +$rt = GHttp::post('https://www.posttestserver.com/post.php',[ + 'name' => 'QueryList', + 'password' => 'ql' +]); + +$rt = GHttp::post('https://www.posttestserver.com/post.php','name=QueryList&password=ql'); + + +$rt = GHttp::postRaw('http://httpbin.org/post','raw data'); +$rt = GHttp::postRaw('http://httpbin.org/post',['aa' => 11,'bb' => 22]); + + +$rt = GHttp::postJson('http://httpbin.org/post',['aa' => 11,'bb' => 22]); +$rt = GHttp::postJson('http://httpbin.org/post','aa=11&bb=22'); + +``` +#### 3.download + +```php +GHttp::download('http://sw.bos.baidu.com/setup.exe','./path/to/xx.exe'); +``` +### 4. concurrent requests +```php +use Jaeger\GHttp; + +$urls = [ + 'http://httpbin.org/get?name=php', + 'http://httpbin.org/get?name=go', + 'http://httpbin.org/get?name=c#', + 'http://httpbin.org/get?name=java' +]; + +GHttp::multiRequest($urls)->withHeaders([ + 'X-Powered-By' => 'Jaeger' +])->withOptions([ + 'timeout' => 10 +])->concurrency(2)->success(function($response,$index){ + print_r((String)$response->getBody()); + print_r($index); +})->error(function($reason,$index){ + print_r($reason); +})->get(); +``` + +```php +use Jaeger\GHttp; +use GuzzleHttp\Psr7\Request; + +$requests = [ + new Request('POST','http://httpbin.org/post',[ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'User-Agent' => 'g-http' + ],http_build_query([ + 'name' => 'php' + ])), + new Request('POST','http://httpbin.org/post',[ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'User-Agent' => 'g-http' + ],http_build_query([ + 'name' => 'go' + ])), + new Request('POST','http://httpbin.org/post',[ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'User-Agent' => 'g-http' + ],http_build_query([ + 'name' => 'c#' + ])) +]; + +GHttp::multiRequest($requests)->success(function($response,$index){ + print_r((String)$response->getBody()); + print_r($index); +})->post(); +``` +### 5. Request with cache + +Base on PHP-Cache: http://www.php-cache.com + +- Use filesystem cache +```php +use Jaeger\GHttp; + +$rt = GHttp::get('http://httpbin.org/get',[ + 'wd' => 'QueryList' +],[ + 'cache' => __DIR__, + 'cache_ttl' => 120 //seconds +]); + +``` + +- Use predis cache + +Install predis adapter: +``` +composer require cache/predis-adapter +``` + +Usage: +```php +use Jaeger\GHttp; +use Cache\Adapter\Predis\PredisCachePool; + +$client = new \Predis\Client('tcp:/127.0.0.1:6379'); +$pool = new PredisCachePool($client); + +$rt = GHttp::get('http://httpbin.org/get',[ + 'wd' => 'QueryList' +],[ + 'cache' => $pool, + 'cache_ttl' => 120 //seconds +]); +``` \ No newline at end of file diff --git a/vendor/jaeger/g-http/composer.json b/vendor/jaeger/g-http/composer.json new file mode 100644 index 0000000..6781037 --- /dev/null +++ b/vendor/jaeger/g-http/composer.json @@ -0,0 +1,20 @@ +{ + "name": "jaeger/g-http", + "description": "Simple Http client base on GuzzleHttp", + "license": "MIT", + "authors": [ + { + "name": "Jaeger", + "email": "JaegerCode@gmail.com" + } + ], + "require": { + "cache/filesystem-adapter": "^1", + "guzzlehttp/guzzle": "^6.0 | ^7.0" + }, + "autoload":{ + "psr-4":{ + "Jaeger\\":"src" + } + } +} diff --git a/vendor/jaeger/g-http/examples/multi_request.php b/vendor/jaeger/g-http/examples/multi_request.php new file mode 100644 index 0000000..b2a138d --- /dev/null +++ b/vendor/jaeger/g-http/examples/multi_request.php @@ -0,0 +1,27 @@ + + * Date: 18/12/10 + * Time: 下午4:04 + */ +require __DIR__.'/../vendor/autoload.php'; +use Jaeger\GHttp; + +$urls = [ + 'http://httpbin.org/get?name=php', + 'http://httpbin.org/get?name=go', + 'http://httpbin.org/get?name=c#', + 'http://httpbin.org/get?name=java' +]; + +GHttp::multiRequest($urls)->withHeaders([ + 'X-Powered-By' => 'Jaeger' +])->withOptions([ + 'timeout' => 10 +])->concurrency(2)->success(function($response,$index){ + print_r((String)$response->getBody()); + print_r($index); +})->error(function($reason,$index){ + print_r($reason); +})->get(); \ No newline at end of file diff --git a/vendor/jaeger/g-http/examples/multi_request_2.php b/vendor/jaeger/g-http/examples/multi_request_2.php new file mode 100644 index 0000000..d66612b --- /dev/null +++ b/vendor/jaeger/g-http/examples/multi_request_2.php @@ -0,0 +1,37 @@ + + * Date: 18/12/10 + * Time: 下午6:51 + */ + +require __DIR__.'/../vendor/autoload.php'; +use Jaeger\GHttp; +use GuzzleHttp\Psr7\Request; + +$requests = [ + new Request('POST','http://httpbin.org/post',[ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'User-Agent' => 'g-http' + ],http_build_query([ + 'name' => 'php' + ])), + new Request('POST','http://httpbin.org/post',[ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'User-Agent' => 'g-http' + ],http_build_query([ + 'name' => 'go' + ])), + new Request('POST','http://httpbin.org/post',[ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'User-Agent' => 'g-http' + ],http_build_query([ + 'name' => 'c#' + ])) +]; + +GHttp::multiRequest($requests)->success(function($response,$index){ + print_r((String)$response->getBody()); + print_r($index); +})->post(); \ No newline at end of file diff --git a/vendor/jaeger/g-http/examples/request_with_cache.php b/vendor/jaeger/g-http/examples/request_with_cache.php new file mode 100644 index 0000000..cdefdbb --- /dev/null +++ b/vendor/jaeger/g-http/examples/request_with_cache.php @@ -0,0 +1,33 @@ + + * Date: 18/12/11 + * Time: 下午6:48 + */ + +require __DIR__.'/../vendor/autoload.php'; +use Jaeger\GHttp; +use Cache\Adapter\Predis\PredisCachePool; + + +$rt = GHttp::get('http://httpbin.org/get',[ + 'wd' => 'QueryList' +],[ + 'cache' => __DIR__, + 'cache_ttl' => 120 +]); + +print_r($rt); + +$client = new \Predis\Client('tcp:/127.0.0.1:6379'); +$pool = new PredisCachePool($client); + +$rt = GHttp::get('http://httpbin.org/get',[ + 'wd' => 'QueryList' +],[ + 'cache' => $pool, + 'cache_ttl' => 120 +]); + +print_r($rt); \ No newline at end of file diff --git a/vendor/jaeger/g-http/examples/simple.php b/vendor/jaeger/g-http/examples/simple.php new file mode 100644 index 0000000..df65510 --- /dev/null +++ b/vendor/jaeger/g-http/examples/simple.php @@ -0,0 +1,22 @@ + + * Date: 18/12/11 + * Time: 下午6:48 + */ + +require __DIR__.'/../vendor/autoload.php'; +use Jaeger\GHttp; + +$rt = GHttp::get('http://httpbin.org/get',[ + 'wd' => 'QueryList' +],[ + 'headers' => [ + 'referer' => 'https://baidu.com', + 'User-Agent' => 'Mozilla/5.0 (Windows NTChrome/58.0.3029.110 Safari/537.36', + 'Cookie' => 'cookie xxx' + ], +]); + +print_r($rt); diff --git a/vendor/jaeger/g-http/src/Cache.php b/vendor/jaeger/g-http/src/Cache.php new file mode 100644 index 0000000..f163a6e --- /dev/null +++ b/vendor/jaeger/g-http/src/Cache.php @@ -0,0 +1,65 @@ + + * Date: 18/12/11 + * Time: 下午6:39 + */ + +namespace Jaeger; + + +use Cache\Adapter\Common\AbstractCachePool; +use Cache\Adapter\Filesystem\FilesystemCachePool; +use League\Flysystem\Adapter\Local; +use League\Flysystem\Filesystem; + +class Cache extends GHttp +{ + public static function remember($name, $arguments) + { + $cachePool = null; + $cacheConfig = self::initCacheConfig($arguments); + + if (empty($cacheConfig['cache'])) { + return self::$name(...$arguments); + } + if (is_string($cacheConfig['cache'])) { + $filesystemAdapter = new Local($cacheConfig['cache']); + $filesystem = new Filesystem($filesystemAdapter); + $cachePool = new FilesystemCachePool($filesystem); + }else if ($cacheConfig['cache'] instanceof AbstractCachePool) { + $cachePool = $cacheConfig['cache']; + } + + $cacheKey = self::getCacheKey($name,$arguments); + $data = $cachePool->get($cacheKey); + if(empty($data)) { + $data = self::$name(...$arguments); + if(!empty($data)) { + $cachePool->set($cacheKey,$data,$cacheConfig['cache_ttl']); + } + } + return $data; + } + + protected static function initCacheConfig($arguments) + { + $cacheConfig = [ + 'cache' => null, + 'cache_ttl' => null + ]; + if(!empty($arguments[2])) { + $cacheConfig = array_merge([ + 'cache' => null, + 'cache_ttl' => null + ],$arguments[2]); + } + return $cacheConfig; + } + + protected static function getCacheKey($name, $arguments) + { + return md5($name.'_'.json_encode($arguments)); + } +} \ No newline at end of file diff --git a/vendor/jaeger/g-http/src/GHttp.php b/vendor/jaeger/g-http/src/GHttp.php new file mode 100644 index 0000000..6293409 --- /dev/null +++ b/vendor/jaeger/g-http/src/GHttp.php @@ -0,0 +1,164 @@ + + * + * @Version V1.0 + */ + +namespace Jaeger; + +use GuzzleHttp\Client; + +/** + * Class GHttp + * @package Jaeger + * + * @method static string get($url,$args = null,$otherArgs = []) + * @method static mixed getJson($url, $args = null, $otherArgs = []) + * @method static string post($url,$args = null,$otherArgs = []) + * @method static string postRaw($url, $raw = null, $otherArgs = []) + * @method static string postJson($url, $args = null, $otherArgs = []) + */ +class GHttp +{ + private static $client = null; + + public static function __callStatic($name, $arguments) + { + $protectedName = '_'.$name; + if(method_exists(self::class,$protectedName)){ + return Cache::remember($protectedName, $arguments); + } + throw new MethodNotFoundException('Call undefined method '.self::class.':'.$name.'()'); + } + + public static function getClient(array $config = []) + { + if(self::$client == null){ + self::$client = new Client($config); + } + return self::$client; + } + + /** + * @param $url + * @param array $args + * @param array $otherArgs + * @return string + */ + protected static function _get($url,$args = null,$otherArgs = []) + { + is_string($args) && parse_str($args,$args); + $args = array_merge([ + 'verify' => false, + 'query' => $args, + 'headers' => [ + 'referer' => $url, + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' + ] + ],$otherArgs); + $client = self::getClient(); + $response = $client->request('GET', $url,$args); + return (string)$response->getBody(); + } + + protected static function _getJson($url, $args = null, $otherArgs = []) + { + $data = self::get($url, $args , $otherArgs); + return json_decode($data,JSON_UNESCAPED_UNICODE); + } + + /** + * @param $url + * @param array $args + * @param array $otherArgs + * @return string + */ + protected static function _post($url,$args = null,$otherArgs = []) + { + is_string($args) && parse_str($args,$args); + $args = array_merge([ + 'verify' => false, + 'form_params' => $args, + 'headers' => [ + 'referer' => $url, + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' + ] + ],$otherArgs); + $client = self::getClient(); + $response = $client->request('Post', $url,$args); + return (string)$response->getBody(); + } + + /** + * @param $url + * @param null $raw + * @param array $otherArgs + * @return string + */ + protected static function _postRaw($url, $raw = null, $otherArgs = []) + { + is_array($raw) && $raw = json_encode($raw); + $args = array_merge([ + 'verify' => false, + 'body' => $raw, + 'headers' => [ + 'referer' => $url, + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' + ] + ],$otherArgs); + $client = self::getClient(); + $response = $client->request('Post', $url,$args); + return (string)$response->getBody(); + } + + /** + * @param $url + * @param null $args + * @param array $otherArgs + * @return string + */ + protected static function _postJson($url, $args = null, $otherArgs = []) + { + is_string($args) && parse_str($args,$args); + $args = array_merge([ + 'verify' => false, + 'json' => $args, + 'headers' => [ + 'referer' => $url, + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' + ] + ],$otherArgs); + $client = self::getClient(); + $response = $client->request('Post', $url,$args); + return (string)$response->getBody(); + } + + /** + * @param $url + * @param $filePath + * @param null $args + * @param array $otherArgs + * @return string + */ + public static function download($url,$filePath,$args = null,$otherArgs = []) + { + $otherArgs = array_merge($otherArgs,[ + 'sink' => $filePath, + ]); + return self::get($url,$args,$otherArgs); + } + + /** + * @param $urls + * @return MultiRequest + */ + public static function multiRequest($urls) + { + $client = self::getClient(); + return MultiRequest::newRequest($client)->urls($urls); + } +} \ No newline at end of file diff --git a/vendor/jaeger/g-http/src/MethodNotFoundException.php b/vendor/jaeger/g-http/src/MethodNotFoundException.php new file mode 100644 index 0000000..dc6e527 --- /dev/null +++ b/vendor/jaeger/g-http/src/MethodNotFoundException.php @@ -0,0 +1,19 @@ + + * Date: 18/12/12 + * Time: 上午10:56 + */ + +namespace Jaeger; +use Exception; +use Throwable; + +class MethodNotFoundException extends Exception +{ + public function __construct($message = "", $code = 0, Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } +} \ No newline at end of file diff --git a/vendor/jaeger/g-http/src/MultiRequest.php b/vendor/jaeger/g-http/src/MultiRequest.php new file mode 100644 index 0000000..d77fc7f --- /dev/null +++ b/vendor/jaeger/g-http/src/MultiRequest.php @@ -0,0 +1,113 @@ + + * Date: 18/12/10 + * Time: 下午6:04 + */ + +namespace Jaeger; +use GuzzleHttp\Client; +use Closure; +use GuzzleHttp\Pool; +use GuzzleHttp\Psr7\Request; + +class MultiRequest +{ + protected $client; + protected $headers = []; + protected $options = []; + protected $successCallback; + protected $errorCallback; + protected $urls = []; + protected $method; + protected $concurrency = 5; + + public function __construct(Client $client) + { + $this->client = $client; + $this->headers = [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' + ]; + } + + public static function newRequest(Client $client) + { + $request = new self($client); + return $request; + } + + public function withHeaders($headers) + { + $this->headers = array_merge($this->headers,$headers); + return $this; + } + + public function withOptions($options) + { + $this->options = $options; + return $this; + } + + public function concurrency($concurrency) + { + $this->concurrency = $concurrency; + return $this; + } + + public function success(Closure $success) + { + $this->successCallback = $success; + return $this; + } + + public function error(Closure $error) + { + $this->errorCallback = $error; + return $this; + } + + public function urls(array $urls) + { + $this->urls = $urls; + return $this; + } + + public function get() + { + $this->method = 'GET'; + $this->send(); + } + + public function post() + { + $this->method = 'POST'; + $this->send(); + } + + protected function send() + { + $client = $this->client; + + $requests = function ($urls) use($client){ + foreach ($urls as $url) { + if (is_string($url)) { + yield new Request($this->method,$url,$this->headers); + } else { + yield $url; + } + } + }; + + $pool = new Pool($client, $requests($this->urls), [ + 'concurrency' => $this->concurrency, + 'fulfilled' => $this->successCallback, + 'rejected' => $this->errorCallback, + 'options' => $this->options + ]); + + $promise = $pool->promise(); + $promise->wait(); + } + +} \ No newline at end of file diff --git a/vendor/jaeger/phpquery-single/README.md b/vendor/jaeger/phpquery-single/README.md new file mode 100644 index 0000000..903163e --- /dev/null +++ b/vendor/jaeger/phpquery-single/README.md @@ -0,0 +1,36 @@ +# phpQuery-single +phpQuery onefile composer.Continuous maintenance,Welcome PR. + +`QueryList` base on phpQuery: https://github.com/jae-jae/QueryList + +phpQuery单文件版本,持续维护,欢迎PR. +> phpQuery项目主页:http://code.google.com/p/phpquery/ + +`QueryList`是基于phpQuery的采集工具: https://github.com/jae-jae/QueryList + +## Composer Installation +Packagist: https://packagist.org/packages/jaeger/phpquery-single +``` +composer require jaeger/phpquery-single +``` + +## Usage +```php +$html = << +
+ QueryList官网 + 这是图片 + 这是图片2 +
+ 其它的一些文本 + +STR; + +$doc = phpQuery::newDocumentHTML($html); + +$src = $doc->find('.two img:eq(0)')->attr('src'); + +echo $src; +// http://querylist.cc/1.jpg +``` \ No newline at end of file diff --git a/vendor/jaeger/phpquery-single/composer.json b/vendor/jaeger/phpquery-single/composer.json new file mode 100644 index 0000000..24fe5a4 --- /dev/null +++ b/vendor/jaeger/phpquery-single/composer.json @@ -0,0 +1,24 @@ +{ + "name": "jaeger/phpquery-single", + "description": "phpQuery单文件版本,是Querylist的依赖(http://querylist.cc/),phpQuery项目主页:http://code.google.com/p/phpquery/", + "homepage": "http://code.google.com/p/phpquery/", + "license": "MIT", + "require": { + "PHP":">=5.3.0" + }, + "authors": [ + { + "name": "Tobiasz Cudnik" + ,"email": "tobiasz.cudnik@gmail.com" + ,"homepage": "https://github.com/TobiaszCudnik" + ,"role": "Developer" + } + ,{ + "name": "Jaeger", + "role": "Packager" + } + ], + "autoload":{ + "classmap":["phpQuery.php"] + } +} diff --git a/vendor/jaeger/phpquery-single/phpQuery.php b/vendor/jaeger/phpquery-single/phpQuery.php new file mode 100644 index 0000000..90391cd --- /dev/null +++ b/vendor/jaeger/phpquery-single/phpQuery.php @@ -0,0 +1,6086 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package phpQuery + */ + +// class names for instanceof +// TODO move them as class constants into phpQuery +define('DOMDOCUMENT', 'DOMDocument'); +define('DOMELEMENT', 'DOMElement'); +define('DOMNODELIST', 'DOMNodeList'); +define('DOMNODE', 'DOMNode'); + +/** + * DOMEvent class. + * + * Based on + * @link http://developer.mozilla.org/En/DOM:event + * @author Tobiasz Cudnik + * @package phpQuery + * @todo implement ArrayAccess ? + */ +class DOMEvent +{ + /** + * Returns a boolean indicating whether the event bubbles up through the DOM or not. + * + * @var unknown_type + */ + public $bubbles = true; + /** + * Returns a boolean indicating whether the event is cancelable. + * + * @var unknown_type + */ + public $cancelable = true; + /** + * Returns a reference to the currently registered target for the event. + * + * @var unknown_type + */ + public $currentTarget; + /** + * Returns detail about the event, depending on the type of event. + * + * @var unknown_type + * @link http://developer.mozilla.org/en/DOM/event.detail + */ + public $detail; // ??? + /** + * Used to indicate which phase of the event flow is currently being evaluated. + * + * NOT IMPLEMENTED + * + * @var unknown_type + * @link http://developer.mozilla.org/en/DOM/event.eventPhase + */ + public $eventPhase; // ??? + /** + * The explicit original target of the event (Mozilla-specific). + * + * NOT IMPLEMENTED + * + * @var unknown_type + */ + public $explicitOriginalTarget; // moz only + /** + * The original target of the event, before any retargetings (Mozilla-specific). + * + * NOT IMPLEMENTED + * + * @var unknown_type + */ + public $originalTarget; // moz only + /** + * Identifies a secondary target for the event. + * + * @var unknown_type + */ + public $relatedTarget; + /** + * Returns a reference to the target to which the event was originally dispatched. + * + * @var unknown_type + */ + public $target; + /** + * Returns the time that the event was created. + * + * @var unknown_type + */ + public $timeStamp; + /** + * Returns the name of the event (case-insensitive). + */ + public $type; + public $runDefault = true; + public $data = null; + public function __construct($data) + { + foreach ($data as $k => $v) { + $this->$k = $v; + } + if (!$this->timeStamp) + $this->timeStamp = time(); + } + /** + * Cancels the event (if it is cancelable). + * + */ + public function preventDefault() + { + $this->runDefault = false; + } + /** + * Stops the propagation of events further along in the DOM. + * + */ + public function stopPropagation() + { + $this->bubbles = false; + } +} + + +/** + * DOMDocumentWrapper class simplifies work with DOMDocument. + * + * Know bug: + * - in XHTML fragments,
changes to
+ * + * @todo check XML catalogs compatibility + * @author Tobiasz Cudnik + * @package phpQuery + */ +class DOMDocumentWrapper +{ + /** + * @var DOMDocument + */ + public $document; + public $id; + /** + * @todo Rewrite as method and quess if null. + * @var unknown_type + */ + public $contentType = ''; + public $xpath; + public $uuid = 0; + public $data = array(); + public $dataNodes = array(); + public $events = array(); + public $eventsNodes = array(); + public $eventsGlobal = array(); + /** + * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28 + * @var unknown_type + */ + public $frames = array(); + /** + * Document root, by default equals to document itself. + * Used by documentFragments. + * + * @var DOMNode + */ + public $root; + public $isDocumentFragment; + public $isXML = false; + public $isXHTML = false; + public $isHTML = false; + public $charset; + public function __construct($markup = null, $contentType = null, $newDocumentID = null) + { + if (isset($markup)) + $this->load($markup, $contentType, $newDocumentID); + $this->id = $newDocumentID + ? $newDocumentID + : md5(microtime()); + } + public function load($markup, $contentType = null, $newDocumentID = null) + { + // phpQuery::$documents[$id] = $this; + $this->contentType = strtolower($contentType); + if ($markup instanceof DOMDOCUMENT) { + $this->document = $markup; + $this->root = $this->document; + $this->charset = $this->document->encoding; + // TODO isDocumentFragment + $loaded = true; + } else { + $loaded = $this->loadMarkup($markup); + } + if ($loaded) { + // $this->document->formatOutput = true; + $this->document->preserveWhiteSpace = true; + $this->xpath = new DOMXPath($this->document); + $this->afterMarkupLoad(); + return true; + // remember last loaded document + // return phpQuery::selectDocument($id); + } + return false; + } + protected function afterMarkupLoad() + { + if ($this->isXHTML) { + $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml"); + } + } + protected function loadMarkup($markup) + { + $loaded = false; + if ($this->contentType) { + self::debug("Load markup for content type {$this->contentType}"); + // content determined by contentType + list($contentType, $charset) = $this->contentTypeToArray($this->contentType); + switch ($contentType) { + case 'text/html': + phpQuery::debug("Loading HTML, content type '{$this->contentType}'"); + $loaded = $this->loadMarkupHTML($markup, $charset); + break; + case 'text/xml': + case 'application/xhtml+xml': + phpQuery::debug("Loading XML, content type '{$this->contentType}'"); + $loaded = $this->loadMarkupXML($markup, $charset); + break; + default: + // for feeds or anything that sometimes doesn't use text/xml + if (strpos('xml', $this->contentType) !== false) { + phpQuery::debug("Loading XML, content type '{$this->contentType}'"); + $loaded = $this->loadMarkupXML($markup, $charset); + } else + phpQuery::debug("Could not determine document type from content type '{$this->contentType}'"); + } + } else { + // content type autodetection + if ($this->isXML($markup)) { + phpQuery::debug("Loading XML, isXML() == true"); + $loaded = $this->loadMarkupXML($markup); + if (!$loaded && $this->isXHTML) { + phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true'); + $loaded = $this->loadMarkupHTML($markup); + } + } else { + phpQuery::debug("Loading HTML, isXML() == false"); + $loaded = $this->loadMarkupHTML($markup); + } + } + return $loaded; + } + protected function loadMarkupReset() + { + $this->isXML = $this->isXHTML = $this->isHTML = false; + } + protected function documentCreate($charset, $version = '1.0') + { + if (!$version) + $version = '1.0'; + $this->document = new DOMDocument($version, $charset); + $this->charset = $this->document->encoding; + // $this->document->encoding = $charset; + $this->document->formatOutput = true; + $this->document->preserveWhiteSpace = true; + } + protected function loadMarkupHTML($markup, $requestedCharset = null) + { + if (phpQuery::$debug) + phpQuery::debug('Full markup load (HTML): ' . substr($markup, 0, 250)); + $this->loadMarkupReset(); + $this->isHTML = true; + if (!isset($this->isDocumentFragment)) + $this->isDocumentFragment = self::isDocumentFragmentHTML($markup); + $charset = null; + $documentCharset = $this->charsetFromHTML($markup); + $addDocumentCharset = false; + if ($documentCharset) { + $charset = $documentCharset; + $markup = $this->charsetFixHTML($markup); + } else if ($requestedCharset) { + $charset = $requestedCharset; + } + if (!$charset) + $charset = phpQuery::$defaultCharset; + // HTTP 1.1 says that the default charset is ISO-8859-1 + // @see http://www.w3.org/International/O-HTTP-charset + if (!$documentCharset) { + $documentCharset = 'ISO-8859-1'; + $addDocumentCharset = true; + } + // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' + // Worse, some pages can have mixed encodings... we'll try not to worry about that + $requestedCharset = $requestedCharset ? strtoupper($requestedCharset) : ""; + $documentCharset = strtoupper($documentCharset); + phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset"); + if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) { + phpQuery::debug("CHARSET CONVERT"); + // Document Encoding Conversion + // http://code.google.com/p/phpquery/issues/detail?id=86 + if (function_exists('mb_detect_encoding')) { + $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO'); + $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets)); + if (!$docEncoding) + $docEncoding = $documentCharset; // ok trust the document + phpQuery::debug("DETECTED '$docEncoding'"); + // Detected does not match what document says... + if ($docEncoding !== $documentCharset) { + // Tricky.. + } + if ($docEncoding !== $requestedCharset) { + phpQuery::debug("CONVERT $docEncoding => $requestedCharset"); + $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding); + $markup = $this->charsetAppendToHTML($markup, $requestedCharset); + $charset = $requestedCharset; + } + } else { + phpQuery::debug("TODO: charset conversion without mbstring..."); + } + } + $return = false; + if ($this->isDocumentFragment) { + phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'"); + $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); + } else { + if ($addDocumentCharset) { + phpQuery::debug("Full markup load (HTML), appending charset: '$charset'"); + $markup = $this->charsetAppendToHTML($markup, $charset); + } + phpQuery::debug("Full markup load (HTML), documentCreate('$charset')"); + $this->documentCreate($charset); + $return = phpQuery::$debug === 2 + ? $this->document->loadHTML($markup) + : @$this->document->loadHTML($markup); + if ($return) + $this->root = $this->document; + } + if ($return && !$this->contentType) + $this->contentType = 'text/html'; + return $return; + } + protected function loadMarkupXML($markup, $requestedCharset = null) + { + if (phpQuery::$debug) + phpQuery::debug('Full markup load (XML): ' . substr($markup, 0, 250)); + $this->loadMarkupReset(); + $this->isXML = true; + // check agains XHTML in contentType or markup + $isContentTypeXHTML = $this->isXHTML(); + $isMarkupXHTML = $this->isXHTML($markup); + if ($isContentTypeXHTML || $isMarkupXHTML) { + self::debug('Full markup load (XML), XHTML detected'); + $this->isXHTML = true; + } + // determine document fragment + if (!isset($this->isDocumentFragment)) + $this->isDocumentFragment = $this->isXHTML + ? self::isDocumentFragmentXHTML($markup) + : self::isDocumentFragmentXML($markup); + // this charset will be used + $charset = null; + // charset from XML declaration @var string + $documentCharset = $this->charsetFromXML($markup); + if (!$documentCharset) { + if ($this->isXHTML) { + // this is XHTML, try to get charset from content-type meta header + $documentCharset = $this->charsetFromHTML($markup); + if ($documentCharset) { + phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'"); + $this->charsetAppendToXML($markup, $documentCharset); + $charset = $documentCharset; + } + } + if (!$documentCharset) { + // if still no document charset... + $charset = $requestedCharset; + } + } else if ($requestedCharset) { + $charset = $requestedCharset; + } + if (!$charset) { + $charset = phpQuery::$defaultCharset; + } + if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) { + // TODO place for charset conversion + // $charset = $requestedCharset; + } + $return = false; + if ($this->isDocumentFragment) { + phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'"); + $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); + } else { + // FIXME ??? + if ($isContentTypeXHTML && !$isMarkupXHTML) + if (!$documentCharset) { + phpQuery::debug("Full markup load (XML), appending charset '$charset'"); + $markup = $this->charsetAppendToXML($markup, $charset); + } + // see http://pl2.php.net/manual/en/book.dom.php#78929 + // LIBXML_DTDLOAD (>= PHP 5.1) + // does XML ctalogues works with LIBXML_NONET + // $this->document->resolveExternals = true; + // TODO test LIBXML_COMPACT for performance improvement + // create document + $this->documentCreate($charset); + if (phpversion() < 5.1) { + $this->document->resolveExternals = true; + $return = phpQuery::$debug === 2 + ? $this->document->loadXML($markup) + : @$this->document->loadXML($markup); + } else { + /** @link http://pl2.php.net/manual/en/libxml.constants.php */ + $libxmlStatic = phpQuery::$debug === 2 + ? LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET + : LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR; + $return = $this->document->loadXML($markup, $libxmlStatic); + // if (! $return) + // $return = $this->document->loadHTML($markup); + } + if ($return) + $this->root = $this->document; + } + if ($return) { + if (!$this->contentType) { + if ($this->isXHTML) + $this->contentType = 'application/xhtml+xml'; + else + $this->contentType = 'text/xml'; + } + return $return; + } else { + throw new Exception("Error loading XML markup"); + } + } + protected function isXHTML($markup = null) + { + if (!isset($markup)) { + return strpos($this->contentType, 'xhtml') !== false; + } + // XXX ok ? + return strpos($markup, "doctype) && is_object($dom->doctype) + // ? $dom->doctype->publicId + // : self::$defaultDoctype; + } + protected function isXML($markup) + { + // return strpos($markup, ']+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', + $markup, + $matches + ); + if (!isset($matches[0])) + return array(null, null); + // get attr 'content' + preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches); + if (!isset($matches[0])) + return array(null, null); + return $this->contentTypeToArray($matches[2]); + } + protected function charsetFromHTML($markup) + { + $contentType = $this->contentTypeFromHTML($markup); + return $contentType[1]; + } + protected function charsetFromXML($markup) + { + $matches; + // find declaration + preg_match( + '@<' . '?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i', + $markup, + $matches + ); + return isset($matches[2]) + ? strtolower($matches[2]) + : null; + } + /** + * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug. + * + * @link http://code.google.com/p/phpquery/issues/detail?id=80 + * @param $html + */ + protected function charsetFixHTML($markup) + { + $matches = array(); + // find meta tag + preg_match( + '@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', + $markup, + $matches, + PREG_OFFSET_CAPTURE + ); + if (!isset($matches[0])) + return; + $metaContentType = $matches[0][0]; + $markup = substr($markup, 0, $matches[0][1]) + . substr($markup, $matches[0][1] + strlen($metaContentType)); + $headStart = stripos($markup, ''); + $markup = substr($markup, 0, $headStart + 6) . $metaContentType + . substr($markup, $headStart + 6); + return $markup; + } + protected function charsetAppendToHTML($html, $charset, $xhtml = false) + { + // remove existing meta[type=content-type] + $html = preg_replace('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html); + $meta = ''; + if (strpos($html, ')@s', + "{$meta}", + $html + ); + } + } else { + return preg_replace( + '@)@s', + '' . $meta, + $html + ); + } + } + protected function charsetAppendToXML($markup, $charset) + { + $declaration = '<' . '?xml version="1.0" encoding="' . $charset . '"?' . '>'; + return $declaration . $markup; + } + public static function isDocumentFragmentHTML($markup) + { + return stripos($markup, 'documentFragmentCreate($node, $sourceCharset); + // if ($fake === false) + // throw new Exception("Error loading documentFragment markup"); + // else + // $return = array_merge($return, + // $this->import($fake->root->childNodes) + // ); + // } else { + // $return[] = $this->document->importNode($node, true); + // } + // } + // return $return; + // } else { + // // string markup + // $fake = $this->documentFragmentCreate($source, $sourceCharset); + // if ($fake === false) + // throw new Exception("Error loading documentFragment markup"); + // else + // return $this->import($fake->root->childNodes); + // } + if (is_array($source) || $source instanceof DOMNODELIST) { + // dom nodes + self::debug('Importing nodes to document'); + foreach ($source as $node) + $return[] = $this->document->importNode($node, true); + } else { + // string markup + $fake = $this->documentFragmentCreate($source, $sourceCharset); + if ($fake === false) + throw new Exception("Error loading documentFragment markup"); + else + return $this->import($fake->root->childNodes); + } + return $return; + } + /** + * Creates new document fragment. + * + * @param $source + * @return DOMDocumentWrapper + */ + protected function documentFragmentCreate($source, $charset = null) + { + $fake = new DOMDocumentWrapper(); + $fake->contentType = $this->contentType; + $fake->isXML = $this->isXML; + $fake->isHTML = $this->isHTML; + $fake->isXHTML = $this->isXHTML; + $fake->root = $fake->document; + if (!$charset) + $charset = $this->charset; + // $fake->documentCreate($this->charset); + if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST)) + $source = array($source); + if (is_array($source) || $source instanceof DOMNODELIST) { + // dom nodes + // load fake document + if (!$this->documentFragmentLoadMarkup($fake, $charset)) + return false; + $nodes = $fake->import($source); + foreach ($nodes as $node) + $fake->root->appendChild($node); + } else { + // string markup + $this->documentFragmentLoadMarkup($fake, $charset, $source); + } + return $fake; + } + /** + * + * @param $document DOMDocumentWrapper + * @param $markup + * @return $document + */ + private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) + { + // TODO error handling + // TODO copy doctype + // tempolary turn off + $fragment->isDocumentFragment = false; + if ($fragment->isXML) { + if ($fragment->isXHTML) { + // add FAKE element to set default namespace + $fragment->loadMarkupXML('' + . '' + . '' . $markup . ''); + $fragment->root = $fragment->document->firstChild->nextSibling; + } else { + $fragment->loadMarkupXML('' . $markup . ''); + $fragment->root = $fragment->document->firstChild; + } + } else { + $markup2 = phpQuery::$defaultDoctype . ''; + if ($markup == null) { + $markup = ""; + } + $noBody = strpos($markup, 'loadMarkupHTML($markup2); + // TODO resolv body tag merging issue + $fragment->root = $noBody + ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling + : $fragment->document->firstChild->nextSibling->firstChild->nextSibling; + } + if (!$fragment->root) + return false; + $fragment->isDocumentFragment = true; + return true; + } + protected function documentFragmentToMarkup($fragment) + { + phpQuery::debug('documentFragmentToMarkup'); + $tmp = $fragment->isDocumentFragment; + $fragment->isDocumentFragment = false; + $markup = $fragment->markup(); + if ($fragment->isXML) { + $markup = substr($markup, 0, strrpos($markup, '')); + if ($fragment->isXHTML) { + $markup = substr($markup, strpos($markup, '') + 6); + } + } else { + $markup = substr($markup, strpos($markup, '') + 6); + $markup = substr($markup, 0, strrpos($markup, '')); + } + $fragment->isDocumentFragment = $tmp; + if (phpQuery::$debug) + phpQuery::debug('documentFragmentToMarkup: ' . substr($markup, 0, 150)); + return $markup; + } + /** + * Return document markup, starting with optional $nodes as root. + * + * @param $nodes DOMNode|DOMNodeList + * @return string + */ + public function markup($nodes = null, $innerMarkup = false) + { + if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT) + $nodes = null; + if (isset($nodes)) { + $markup = ''; + if (!is_array($nodes) && !($nodes instanceof DOMNODELIST)) + $nodes = array($nodes); + if ($this->isDocumentFragment && !$innerMarkup) + foreach ($nodes as $i => $node) + if ($node->isSameNode($this->root)) { + // var_dump($node); + $nodes = array_slice($nodes, 0, $i) + + phpQuery::DOMNodeListToArray($node->childNodes) + + array_slice($nodes, $i + 1); + } + if ($this->isXML && !$innerMarkup) { + self::debug("Getting outerXML with charset '{$this->charset}'"); + // we need outerXML, so we can benefit from + // $node param support in saveXML() + foreach ($nodes as $node) + $markup .= $this->document->saveXML($node); + } else { + $loop = array(); + if ($innerMarkup) + foreach ($nodes as $node) { + if ($node->childNodes) + foreach ($node->childNodes as $child) + $loop[] = $child; + else + $loop[] = $node; + } + else + $loop = $nodes; + self::debug("Getting markup, moving selected nodes (" . count($loop) . ") to new DocumentFragment"); + $fake = $this->documentFragmentCreate($loop); + $markup = $this->documentFragmentToMarkup($fake); + } + if ($this->isXHTML) { + self::debug("Fixing XHTML"); + $markup = self::markupFixXHTML($markup); + } + self::debug("Markup: " . substr($markup, 0, 250)); + return $markup; + } else { + if ($this->isDocumentFragment) { + // documentFragment, html only... + self::debug("Getting markup, DocumentFragment detected"); + // return $this->markup( + //// $this->document->getElementsByTagName('body')->item(0) + // $this->document->root, true + // ); + $markup = $this->documentFragmentToMarkup($this); + // no need for markupFixXHTML, as it's done thought markup($nodes) method + return $markup; + } else { + self::debug("Getting markup (" . ($this->isXML ? 'XML' : 'HTML') . "), final with charset '{$this->charset}'"); + $markup = $this->isXML + ? $this->document->saveXML() + : $this->document->saveHTML(); + if ($this->isXHTML) { + self::debug("Fixing XHTML"); + $markup = self::markupFixXHTML($markup); + } + self::debug("Markup: " . substr($markup, 0, 250)); + return $markup; + } + } + } + protected static function markupFixXHTML($markup) + { + $markup = self::expandEmptyTag('script', $markup); + $markup = self::expandEmptyTag('select', $markup); + $markup = self::expandEmptyTag('textarea', $markup); + return $markup; + } + public static function debug($text) + { + phpQuery::debug($text); + } + /** + * expandEmptyTag + * + * @param $tag + * @param $xml + * @return unknown_type + * @author mjaque at ilkebenson dot com + * @link http://php.net/manual/en/domdocument.savehtml.php#81256 + */ + public static function expandEmptyTag($tag, $xml) + { + $indice = 0; + while ($indice < strlen($xml)) { + $pos = strpos($xml, "<$tag ", $indice); + if ($pos) { + $posCierre = strpos($xml, ">", $pos); + if ($xml[$posCierre - 1] == "/") { + $xml = substr_replace($xml, ">", $posCierre - 1, 2); + } + $indice = $posCierre; + } else break; + } + return $xml; + } +} + +/** + * Event handling class. + * + * @author Tobiasz Cudnik + * @package phpQuery + * @static + */ +abstract class phpQueryEvents +{ + /** + * Trigger a type of event on every matched element. + * + * @param DOMNode|phpQueryObject|string $document + * @param unknown_type $type + * @param unknown_type $data + * + * @TODO exclusive events (with !) + * @TODO global events (test) + * @TODO support more than event in $type (space-separated) + */ + public static function trigger($document, $type, $data = array(), $node = null) + { + // trigger: function(type, data, elem, donative, extra) { + $documentID = phpQuery::getDocumentID($document); + $namespace = null; + if (strpos($type, '.') !== false) + list($name, $namespace) = explode('.', $type); + else + $name = $type; + if (!$node) { + if (self::issetGlobal($documentID, $type)) { + $pq = phpQuery::getDocument($documentID); + // TODO check add($pq->document) + $pq->find('*')->add($pq->document) + ->trigger($type, $data); + } + } else { + if (isset($data[0]) && $data[0] instanceof DOMEvent) { + $event = $data[0]; + $event->relatedTarget = $event->target; + $event->target = $node; + $data = array_slice($data, 1); + } else { + $event = new DOMEvent(array( + 'type' => $type, + 'target' => $node, + 'timeStamp' => time(), + )); + } + $i = 0; + while ($node) { + // TODO whois + phpQuery::debug("Triggering " . ($i ? "bubbled " : '') . "event '{$type}' on " + . "node \n"); //.phpQueryObject::whois($node)."\n"); + $event->currentTarget = $node; + $eventNode = self::getNode($documentID, $node); + if (isset($eventNode->eventHandlers)) { + foreach ($eventNode->eventHandlers as $eventType => $handlers) { + $eventNamespace = null; + if (strpos($type, '.') !== false) + list($eventName, $eventNamespace) = explode('.', $eventType); + else + $eventName = $eventType; + if ($name != $eventName) + continue; + if ($namespace && $eventNamespace && $namespace != $eventNamespace) + continue; + foreach ($handlers as $handler) { + phpQuery::debug("Calling event handler\n"); + $event->data = $handler['data'] + ? $handler['data'] + : null; + $params = array_merge(array($event), $data); + $return = phpQuery::callbackRun($handler['callback'], $params); + if ($return === false) { + $event->bubbles = false; + } + } + } + } + // to bubble or not to bubble... + if (!$event->bubbles) + break; + $node = $node->parentNode; + $i++; + } + } + } + /** + * Binds a handler to one or more events (like click) for each matched element. + * Can also bind custom events. + * + * @param DOMNode|phpQueryObject|string $document + * @param unknown_type $type + * @param unknown_type $data Optional + * @param unknown_type $callback + * + * @TODO support '!' (exclusive) events + * @TODO support more than event in $type (space-separated) + * @TODO support binding to global events + */ + public static function add($document, $node, $type, $data, $callback = null) + { + phpQuery::debug("Binding '$type' event"); + $documentID = phpQuery::getDocumentID($document); + // if (is_null($callback) && is_callable($data)) { + // $callback = $data; + // $data = null; + // } + $eventNode = self::getNode($documentID, $node); + if (!$eventNode) + $eventNode = self::setNode($documentID, $node); + if (!isset($eventNode->eventHandlers[$type])) + $eventNode->eventHandlers[$type] = array(); + $eventNode->eventHandlers[$type][] = array( + 'callback' => $callback, + 'data' => $data, + ); + } + /** + * Enter description here... + * + * @param DOMNode|phpQueryObject|string $document + * @param unknown_type $type + * @param unknown_type $callback + * + * @TODO namespace events + * @TODO support more than event in $type (space-separated) + */ + public static function remove($document, $node, $type = null, $callback = null) + { + $documentID = phpQuery::getDocumentID($document); + $eventNode = self::getNode($documentID, $node); + if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) { + if ($callback) { + foreach ($eventNode->eventHandlers[$type] as $k => $handler) + if ($handler['callback'] == $callback) + unset($eventNode->eventHandlers[$type][$k]); + } else { + unset($eventNode->eventHandlers[$type]); + } + } + } + protected static function getNode($documentID, $node) + { + foreach (phpQuery::$documents[$documentID]->eventsNodes as $eventNode) { + if ($node->isSameNode($eventNode)) + return $eventNode; + } + } + protected static function setNode($documentID, $node) + { + phpQuery::$documents[$documentID]->eventsNodes[] = $node; + return phpQuery::$documents[$documentID]->eventsNodes[count(phpQuery::$documents[$documentID]->eventsNodes) - 1]; + } + protected static function issetGlobal($documentID, $type) + { + return isset(phpQuery::$documents[$documentID]) + ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal) + : false; + } +} + + +interface ICallbackNamed +{ + function hasName(); + function getName(); +} +/** + * Callback class introduces currying-like pattern. + * + * Example: + * function foo($param1, $param2, $param3) { + * var_dump($param1, $param2, $param3); + * } + * $fooCurried = new Callback('foo', + * 'param1 is now statically set', + * new CallbackParam, new CallbackParam + * ); + * phpQuery::callbackRun($fooCurried, + * array('param2 value', 'param3 value' + * ); + * + * Callback class is supported in all phpQuery methods which accepts callbacks. + * + * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures + * @author Tobiasz Cudnik + * + * @TODO??? return fake forwarding function created via create_function + * @TODO honor paramStructure + */ +class Callback +implements ICallbackNamed +{ + public $callback = null; + public $params = null; + protected $name; + public function __construct( + $callback, + $param1 = null, + $param2 = null, + $param3 = null + ) { + $params = func_get_args(); + $params = array_slice($params, 1); + if ($callback instanceof Callback) { + // TODO implement recurention + } else { + $this->callback = $callback; + $this->params = $params; + } + } + public function getName() + { + return 'Callback: ' . $this->name; + } + public function hasName() + { + return isset($this->name) && $this->name; + } + public function setName($name) + { + $this->name = $name; + return $this; + } + // TODO test me + // public function addParams() { + // $params = func_get_args(); + // return new Callback($this->callback, $this->params+$params); + // } +} +/** + * Shorthand for new Callback(create_function(...), ...); + * + * @author Tobiasz Cudnik + */ +class CallbackBody extends Callback +{ + public function __construct( + $paramList, + $code, + $param1 = null, + $param2 = null, + $param3 = null + ) { + $params = func_get_args(); + $params = array_slice($params, 2); + $this->callback = create_function($paramList, $code); + $this->params = $params; + } +} +/** + * Callback type which on execution returns reference passed during creation. + * + * @author Tobiasz Cudnik + */ +class CallbackReturnReference extends Callback +implements ICallbackNamed +{ + protected $reference; + public function __construct(&$reference, $name = null) + { + $this->reference = &$reference; + $this->callback = array($this, 'callback'); + } + public function callback() + { + return $this->reference; + } + public function getName() + { + return 'Callback: ' . $this->name; + } + public function hasName() + { + return isset($this->name) && $this->name; + } +} +/** + * Callback type which on execution returns value passed during creation. + * + * @author Tobiasz Cudnik + */ +class CallbackReturnValue extends Callback +implements ICallbackNamed +{ + protected $value; + protected $name; + public function __construct($value, $name = null) + { + $this->value = &$value; + $this->name = $name; + $this->callback = array($this, 'callback'); + } + public function callback() + { + return $this->value; + } + public function __toString() + { + return $this->getName(); + } + public function getName() + { + return 'Callback: ' . $this->name; + } + public function hasName() + { + return isset($this->name) && $this->name; + } +} +/** + * CallbackParameterToReference can be used when we don't really want a callback, + * only parameter passed to it. CallbackParameterToReference takes first + * parameter's value and passes it to reference. + * + * @author Tobiasz Cudnik + */ +class CallbackParameterToReference extends Callback +{ + /** + * @param $reference + * @TODO implement $paramIndex; + * param index choose which callback param will be passed to reference + */ + public function __construct(&$reference) + { + $this->callback = &$reference; + } +} +//class CallbackReference extends Callback { +// /** +// * +// * @param $reference +// * @param $paramIndex +// * @todo implement $paramIndex; param index choose which callback param will be passed to reference +// */ +// public function __construct(&$reference, $name = null){ +// $this->callback =& $reference; +// } +//} +class CallbackParam +{ +} + +/** + * Class representing phpQuery objects. + * + * @author Tobiasz Cudnik + * @package phpQuery + * @method phpQueryObject clone() clone() + * @method phpQueryObject empty() empty() + * @method phpQueryObject next() next($selector = null) + * @method phpQueryObject prev() prev($selector = null) + * @property Int $length + */ +class phpQueryObject +implements Iterator, Countable, ArrayAccess +{ + public $documentID = null; + /** + * DOMDocument class. + * + * @var DOMDocument + */ + public $document = null; + public $charset = null; + /** + * + * @var DOMDocumentWrapper + */ + public $documentWrapper = null; + /** + * XPath interface. + * + * @var DOMXPath + */ + public $xpath = null; + /** + * Stack of selected elements. + * @TODO refactor to ->nodes + * @var array + */ + public $elements = array(); + /** + * @access private + */ + protected $elementsBackup = array(); + /** + * @access private + */ + protected $previous = null; + /** + * @access private + * @TODO deprecate + */ + protected $root = array(); + /** + * Indicated if doument is just a fragment (no tag). + * + * Every document is realy a full document, so even documentFragments can + * be queried against , but getDocument(id)->htmlOuter() will return + * only contents of . + * + * @var bool + */ + public $documentFragment = true; + /** + * Iterator interface helper + * @access private + */ + protected $elementsInterator = array(); + /** + * Iterator interface helper + * @access private + */ + protected $valid = false; + /** + * Iterator interface helper + * @access private + */ + protected $current = null; + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function __construct($documentID) + { + // if ($documentID instanceof self) + // var_dump($documentID->getDocumentID()); + $id = $documentID instanceof self + ? $documentID->getDocumentID() + : $documentID; + // var_dump($id); + if (!isset(phpQuery::$documents[$id])) { + // var_dump(phpQuery::$documents); + throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first."); + } + $this->documentID = $id; + $this->documentWrapper = &phpQuery::$documents[$id]; + $this->document = &$this->documentWrapper->document; + $this->xpath = &$this->documentWrapper->xpath; + $this->charset = &$this->documentWrapper->charset; + $this->documentFragment = &$this->documentWrapper->isDocumentFragment; + // TODO check $this->DOM->documentElement; + // $this->root = $this->document->documentElement; + $this->root = &$this->documentWrapper->root; + // $this->toRoot(); + $this->elements = array($this->root); + } + /** + * + * @access private + * @param $attr + * @return unknown_type + */ + public function __get($attr) + { + switch ($attr) { + // FIXME doesnt work at all ? + case 'length': + return $this->size(); + break; + default: + return $this->$attr; + } + } + /** + * Saves actual object to $var by reference. + * Useful when need to break chain. + * @param phpQueryObject $var + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function toReference(&$var) + { + return $var = $this; + } + public function documentFragment($state = null) + { + if ($state) { + phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state; + return $this; + } + return $this->documentFragment; + } + /** + * @access private + * @TODO documentWrapper + */ + protected function isRoot($node) + { + // return $node instanceof DOMDOCUMENT || $node->tagName == 'html'; + return $node instanceof DOMDOCUMENT + || ($node instanceof DOMELEMENT && $node->tagName == 'html') + || $this->root->isSameNode($node); + } + /** + * @access private + */ + protected function stackIsRoot() + { + return $this->size() == 1 && $this->isRoot($this->elements[0]); + } + /** + * Enter description here... + * NON JQUERY METHOD + * + * Watch out, it doesn't creates new instance, can be reverted with end(). + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function toRoot() + { + $this->elements = array($this->root); + return $this; + // return $this->newInstance(array($this->root)); + } + /** + * Saves object's DocumentID to $var by reference. + * + * $myDocumentId; + * phpQuery::newDocument('
') + * ->getDocumentIDRef($myDocumentId) + * ->find('div')->... + * + * + * @param unknown_type $domId + * @see phpQuery::newDocument + * @see phpQuery::newDocumentFile + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function getDocumentIDRef(&$documentID) + { + $documentID = $this->getDocumentID(); + return $this; + } + /** + * Returns object with stack set to document root. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function getDocument() + { + return phpQuery::getDocument($this->getDocumentID()); + } + /** + * + * @return DOMDocument + */ + public function getDOMDocument() + { + return $this->document; + } + /** + * Get object's Document ID. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function getDocumentID() + { + return $this->documentID; + } + /** + * Unloads whole document from memory. + * CAUTION! None further operations will be possible on this document. + * All objects refering to it will be useless. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function unloadDocument() + { + phpQuery::unloadDocuments($this->getDocumentID()); + } + public function isHTML() + { + return $this->documentWrapper->isHTML; + } + public function isXHTML() + { + return $this->documentWrapper->isXHTML; + } + public function isXML() + { + return $this->documentWrapper->isXML; + } + /** + * Enter description here... + * + * @link http://docs.jquery.com/Ajax/serialize + * @return string + */ + public function serialize() + { + return phpQuery::param($this->serializeArray()); + } + /** + * Enter description here... + * + * @link http://docs.jquery.com/Ajax/serializeArray + * @return array + */ + public function serializeArray($submit = null) + { + $source = $this->filter('form, input, select, textarea') + ->find('input, select, textarea') + ->andSelf() + ->not('form'); + $return = array(); + // $source->dumpDie(); + foreach ($source as $input) { + $input = phpQuery::pq($input); + if ($input->is('[disabled]')) + continue; + if (!$input->is('[name]')) + continue; + if ($input->is('[type=checkbox]') && !$input->is('[checked]')) + continue; + // jquery diff + if ($submit && $input->is('[type=submit]')) { + if ($submit instanceof DOMELEMENT && !$input->elements[0]->isSameNode($submit)) + continue; + else if (is_string($submit) && $input->attr('name') != $submit) + continue; + } + $return[] = array( + 'name' => $input->attr('name'), + 'value' => $input->val(), + ); + } + return $return; + } + /** + * @access private + */ + protected function debug($in) + { + if (!phpQuery::$debug) + return; + print('
');
+		print_r($in);
+		// file debug
+		//		file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
+		// quite handy debug trace
+		//		if ( is_array($in))
+		//			print_r(array_slice(debug_backtrace(), 3));
+		print("
\n"); + } + /** + * @access private + */ + protected function isRegexp($pattern) + { + return in_array( + $pattern[mb_strlen($pattern) - 1], + array('^', '*', '$') + ); + } + /** + * Determines if $char is really a char. + * + * @param string $char + * @return bool + * @todo rewrite me to charcode range ! ;) + * @access private + */ + protected function isChar($char) + { + return extension_loaded('mbstring') && phpQuery::$mbstringSupport + ? mb_eregi('\w', $char) + : preg_match('@\w@', $char); + } + /** + * @access private + */ + protected function parseSelector($query) + { + // clean spaces + // TODO include this inside parsing ? + $query = trim( + preg_replace( + '@\s+@', + ' ', + preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) + ) + ); + $queries = array(array()); + if (!$query) + return $queries; + $return = &$queries[0]; + $specialChars = array('>', ' '); + // $specialCharsMapping = array('/' => '>'); + $specialCharsMapping = array(); + $strlen = mb_strlen($query); + $classChars = array('.', '-'); + $pseudoChars = array('-'); + $tagChars = array('*', '|', '-'); + // split multibyte string + // http://code.google.com/p/phpquery/issues/detail?id=76 + $_query = array(); + for ($i = 0; $i < $strlen; $i++) + $_query[] = mb_substr($query, $i, 1); + $query = $_query; + // it works, but i dont like it... + $i = 0; + while ($i < $strlen) { + $c = $query[$i]; + $tmp = ''; + // TAG + if ($this->isChar($c) || in_array($c, $tagChars)) { + while ( + isset($query[$i]) + && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars)) + ) { + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // IDs + } else if ($c == '#') { + $i++; + while (isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) { + $tmp .= $query[$i]; + $i++; + } + $return[] = '#' . $tmp; + // SPECIAL CHARS + } else if (in_array($c, $specialChars)) { + $return[] = $c; + $i++; + // MAPPED SPECIAL MULTICHARS + // } else if ( $c.$query[$i+1] == '//') { + // $return[] = ' '; + // $i = $i+2; + // MAPPED SPECIAL CHARS + } else if (isset($specialCharsMapping[$c])) { + $return[] = $specialCharsMapping[$c]; + $i++; + // COMMA + } else if ($c == ',') { + $queries[] = array(); + $return = &$queries[count($queries) - 1]; + $i++; + while (isset($query[$i]) && $query[$i] == ' ') + $i++; + // CLASSES + } else if ($c == '.') { + while (isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) { + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // ~ General Sibling Selector + } else if ($c == '~') { + $spaceAllowed = true; + $tmp .= $query[$i++]; + while ( + isset($query[$i]) + && ($this->isChar($query[$i]) + || in_array($query[$i], $classChars) + || $query[$i] == '*' + || ($query[$i] == ' ' && $spaceAllowed) + ) + ) { + if ($query[$i] != ' ') + $spaceAllowed = false; + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // + Adjacent sibling selectors + } else if ($c == '+') { + $spaceAllowed = true; + $tmp .= $query[$i++]; + while ( + isset($query[$i]) + && ($this->isChar($query[$i]) + || in_array($query[$i], $classChars) + || $query[$i] == '*' + || ($spaceAllowed && $query[$i] == ' ') + ) + ) { + if ($query[$i] != ' ') + $spaceAllowed = false; + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // ATTRS + } else if ($c == '[') { + $stack = 1; + $tmp .= $c; + while (isset($query[++$i])) { + $tmp .= $query[$i]; + if ($query[$i] == '[') { + $stack++; + } else if ($query[$i] == ']') { + $stack--; + if (!$stack) + break; + } + } + $return[] = $tmp; + $i++; + // PSEUDO CLASSES + } else if ($c == ':') { + $stack = 1; + $tmp .= $query[$i++]; + while (isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) { + $tmp .= $query[$i]; + $i++; + } + // with arguments ? + if (isset($query[$i]) && $query[$i] == '(') { + $tmp .= $query[$i]; + $stack = 1; + while (isset($query[++$i])) { + $tmp .= $query[$i]; + if ($query[$i] == '(') { + $stack++; + } else if ($query[$i] == ')') { + $stack--; + if (!$stack) + break; + } + } + $return[] = $tmp; + $i++; + } else { + $return[] = $tmp; + } + } else { + $i++; + } + } + foreach ($queries as $k => $q) { + if (isset($q[0])) { + if (isset($q[0][0]) && $q[0][0] == ':') + array_unshift($queries[$k], '*'); + if ($q[0] != '>') + array_unshift($queries[$k], ' '); + } + } + return $queries; + } + /** + * Return matched DOM nodes. + * + * @param int $index + * @return array|DOMElement Single DOMElement or array of DOMElement. + */ + public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) + { + $return = isset($index) + ? (isset($this->elements[$index]) ? $this->elements[$index] : null) + : $this->elements; + // pass thou callbacks + $args = func_get_args(); + $args = array_slice($args, 1); + foreach ($args as $callback) { + if (is_array($return)) + foreach ($return as $k => $v) + $return[$k] = phpQuery::callbackRun($callback, array($v)); + else + $return = phpQuery::callbackRun($callback, array($return)); + } + return $return; + } + /** + * Return matched DOM nodes. + * jQuery difference. + * + * @param int $index + * @return array|string Returns string if $index != null + * @todo implement callbacks + * @todo return only arrays ? + * @todo maybe other name... + */ + public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) + { + if (!is_null($index) && is_int($index)) + $return = $this->eq($index)->text(); + else { + $return = array(); + for ($i = 0; $i < $this->size(); $i++) { + $return[] = $this->eq($i)->text(); + } + } + // pass thou callbacks + $args = func_get_args(); + $args = array_slice($args, 1); + foreach ($args as $callback) { + $return = phpQuery::callbackRun($callback, array($return)); + } + return $return; + } + /** + * Return matched DOM nodes. + * jQuery difference. + * + * @param int $index + * @return array|string Returns string if $index != null + * @todo implement callbacks + * @todo return only arrays ? + * @todo maybe other name... + */ + public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) + { + if (!is_null($index) && is_int($index)) + $return = $this->eq($index)->text(); + else { + $return = array(); + for ($i = 0; $i < $this->size(); $i++) { + $return[] = $this->eq($i)->text(); + } + // pass thou callbacks + $args = func_get_args(); + $args = array_slice($args, 1); + } + foreach ($args as $callback) { + if (is_array($return)) + foreach ($return as $k => $v) + $return[$k] = phpQuery::callbackRun($callback, array($v)); + else + $return = phpQuery::callbackRun($callback, array($return)); + } + return $return; + } + /** + * Returns new instance of actual class. + * + * @param array $newStack Optional. Will replace old stack with new and move old one to history.c + */ + public function newInstance($newStack = null) + { + $class = get_class($this); + // support inheritance by passing old object to overloaded constructor + $new = $class != 'phpQuery' + ? new $class($this, $this->getDocumentID()) + : new phpQueryObject($this->getDocumentID()); + $new->previous = $this; + if (is_null($newStack)) { + $new->elements = $this->elements; + if ($this->elementsBackup) + $this->elements = $this->elementsBackup; + } else if (is_string($newStack)) { + $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack(); + } else { + $new->elements = $newStack; + } + return $new; + } + /** + * Enter description here... + * + * In the future, when PHP will support XLS 2.0, then we would do that this way: + * contains(tokenize(@class, '\s'), "something") + * @param unknown_type $class + * @param unknown_type $node + * @return boolean + * @access private + */ + protected function matchClasses($class, $node) + { + // multi-class + if (mb_strpos($class, '.', 1)) { + $classes = explode('.', substr($class, 1)); + $classesCount = count($classes); + $nodeClasses = explode(' ', $node->getAttribute('class')); + $nodeClassesCount = count($nodeClasses); + if ($classesCount > $nodeClassesCount) + return false; + $diff = count( + array_diff( + $classes, + $nodeClasses + ) + ); + if (!$diff) + return true; + // single-class + } else { + return in_array( + // strip leading dot from class name + substr($class, 1), + // get classes for element as array + explode(' ', $node->getAttribute('class')) + ); + } + } + /** + * @access private + */ + protected function runQuery($XQuery, $selector = null, $compare = null) + { + if ($compare && !method_exists($this, $compare)) + return false; + $stack = array(); + if (!$this->elements) + $this->debug('Stack empty, skipping...'); + // var_dump($this->elements[0]->nodeType); + // element, document + foreach ($this->stack(array(1, 9, 13)) as $k => $stackNode) { + $detachAfter = false; + // to work on detached nodes we need temporary place them somewhere + // thats because context xpath queries sucks ;] + $testNode = $stackNode; + while ($testNode) { + if (!$testNode->parentNode && !$this->isRoot($testNode)) { + $this->root->appendChild($testNode); + $detachAfter = $testNode; + break; + } + $testNode = isset($testNode->parentNode) + ? $testNode->parentNode + : null; + } + // XXX tmp ? + $xpath = $this->documentWrapper->isXHTML + ? $this->getNodeXpath($stackNode, 'html') + : $this->getNodeXpath($stackNode); + // FIXME pseudoclasses-only query, support XML + $query = $XQuery == '//' && $xpath == '/html[1]' + ? '//*' + : $xpath . $XQuery; + $this->debug("XPATH: {$query}"); + // run query, get elements + $nodes = $this->xpath->query($query); + $this->debug("QUERY FETCHED"); + if (!$nodes->length) + $this->debug('Nothing found'); + $debug = array(); + foreach ($nodes as $node) { + $matched = false; + if ($compare) { + phpQuery::$debug ? + $this->debug("Found: " . $this->whois($node) . ", comparing with {$compare}()") + : null; + $phpQueryDebug = phpQuery::$debug; + phpQuery::$debug = false; + // TODO ??? use phpQuery::callbackRun() + if (call_user_func_array(array($this, $compare), array($selector, $node))) + $matched = true; + phpQuery::$debug = $phpQueryDebug; + } else { + $matched = true; + } + if ($matched) { + if (phpQuery::$debug) + $debug[] = $this->whois($node); + $stack[] = $node; + } + } + if (phpQuery::$debug) { + $this->debug("Matched " . count($debug) . ": " . implode(', ', $debug)); + } + if ($detachAfter) + $this->root->removeChild($detachAfter); + } + $this->elements = $stack; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function find($selectors, $context = null, $noHistory = false) + { + if (!$noHistory) + // backup last stack /for end()/ + $this->elementsBackup = $this->elements; + // allow to define context + // TODO combine code below with phpQuery::pq() context guessing code + // as generic function + if ($context) { + if (!is_array($context) && $context instanceof DOMELEMENT) + $this->elements = array($context); + else if (is_array($context)) { + $this->elements = array(); + foreach ($context as $c) + if ($c instanceof DOMELEMENT) + $this->elements[] = $c; + } else if ($context instanceof self) + $this->elements = $context->elements; + } + $queries = $this->parseSelector($selectors); + $this->debug(array('FIND', $selectors, $queries)); + $XQuery = ''; + // remember stack state because of multi-queries + $oldStack = $this->elements; + // here we will be keeping found elements + $stack = array(); + foreach ($queries as $selector) { + $this->elements = $oldStack; + $delimiterBefore = false; + foreach ($selector as $s) { + // TAG + $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport + ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*' + : preg_match('@^[\w|\||-]+$@', $s) || $s == '*'; + if ($isTag) { + if ($this->isXML()) { + // namespace support + if (mb_strpos($s, '|') !== false) { + $ns = $tag = null; + list($ns, $tag) = explode('|', $s); + $XQuery .= "$ns:$tag"; + } else if ($s == '*') { + $XQuery .= "*"; + } else { + $XQuery .= "*[local-name()='$s']"; + } + } else { + $XQuery .= $s; + } + // ID + } else if ($s[0] == '#') { + if ($delimiterBefore) + $XQuery .= '*'; + $XQuery .= "[@id='" . substr($s, 1) . "']"; + // ATTRIBUTES + } else if ($s[0] == '[') { + if ($delimiterBefore) + $XQuery .= '*'; + // strip side brackets + $attr = trim($s, ']['); + $execute = false; + // attr with specifed value + if (mb_strpos($s, '=')) { + $value = null; + list($attr, $value) = explode('=', $attr); + $value = trim($value, "'\""); + if ($this->isRegexp($attr)) { + // cut regexp character + $attr = substr($attr, 0, -1); + $execute = true; + $XQuery .= "[@{$attr}]"; + } else { + $XQuery .= "[@{$attr}='{$value}']"; + } + // attr without specified value + } else { + $XQuery .= "[@{$attr}]"; + } + if ($execute) { + $this->runQuery($XQuery, $s, 'is'); + $XQuery = ''; + if (!$this->length()) + break; + } + // CLASSES + } else if ($s[0] == '.') { + // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]"); + // thx wizDom ;) + if ($delimiterBefore) + $XQuery .= '*'; + $XQuery .= '[@class]'; + $this->runQuery($XQuery, $s, 'matchClasses'); + $XQuery = ''; + if (!$this->length()) + break; + // ~ General Sibling Selector + } else if ($s[0] == '~') { + $this->runQuery($XQuery); + $XQuery = ''; + $this->elements = $this + ->siblings( + substr($s, 1) + )->elements; + if (!$this->length()) + break; + // + Adjacent sibling selectors + } else if ($s[0] == '+') { + // TODO /following-sibling:: + $this->runQuery($XQuery); + $XQuery = ''; + $subSelector = substr($s, 1); + $subElements = $this->elements; + $this->elements = array(); + foreach ($subElements as $node) { + // search first DOMElement sibling + $test = $node->nextSibling; + while ($test && !($test instanceof DOMELEMENT)) + $test = $test->nextSibling; + if ($test && $this->is($subSelector, $test)) + $this->elements[] = $test; + } + if (!$this->length()) + break; + // PSEUDO CLASSES + } else if ($s[0] == ':') { + // TODO optimization for :first :last + if ($XQuery) { + $this->runQuery($XQuery); + $XQuery = ''; + } + if (!$this->length()) + break; + $this->pseudoClasses($s); + if (!$this->length()) + break; + // DIRECT DESCENDANDS + } else if ($s == '>') { + $XQuery .= '/'; + $delimiterBefore = 2; + // ALL DESCENDANDS + } else if ($s == ' ') { + $XQuery .= '//'; + $delimiterBefore = 2; + // ERRORS + } else { + phpQuery::debug("Unrecognized token '$s'"); + } + $delimiterBefore = $delimiterBefore === 2; + } + // run query if any + if ($XQuery && $XQuery != '//') { + $this->runQuery($XQuery); + $XQuery = ''; + } + foreach ($this->elements as $node) + if (!$this->elementsContainsNode($node, $stack)) + $stack[] = $node; + } + $this->elements = $stack; + return $this->newInstance(); + } + /** + * @todo create API for classes with pseudoselectors + * @access private + */ + protected function pseudoClasses($class) + { + // TODO clean args parsing ? + $class = ltrim($class, ':'); + $haveArgs = mb_strpos($class, '('); + if ($haveArgs !== false) { + $args = substr($class, $haveArgs + 1, -1); + $class = substr($class, 0, $haveArgs); + } + switch ($class) { + case 'even': + case 'odd': + $stack = array(); + foreach ($this->elements as $i => $node) { + if ($class == 'even' && ($i % 2) == 0) + $stack[] = $node; + else if ($class == 'odd' && $i % 2) + $stack[] = $node; + } + $this->elements = $stack; + break; + case 'eq': + $k = intval($args); + if ($k < 0) { + $this->elements = array($this->elements[count($this->elements) + $k]); + } else { + $this->elements = isset($this->elements[$k]) + ? array($this->elements[$k]) + : array(); + } + break; + case 'gt': + $this->elements = array_slice($this->elements, $args + 1); + break; + case 'lt': + $this->elements = array_slice($this->elements, 0, $args + 1); + break; + case 'first': + if (isset($this->elements[0])) + $this->elements = array($this->elements[0]); + break; + case 'last': + if ($this->elements) + $this->elements = array($this->elements[count($this->elements) - 1]); + break; + /*case 'parent': + $stack = array(); + foreach($this->elements as $node) { + if ( $node->childNodes->length ) + $stack[] = $node; + } + $this->elements = $stack; + break;*/ + case 'contains': + $text = trim($args, "\"'"); + $stack = array(); + foreach ($this->elements as $node) { + if (mb_stripos($node->textContent, $text) === false) + continue; + $stack[] = $node; + } + $this->elements = $stack; + break; + case 'not': + $selector = self::unQuote($args); + $this->elements = $this->not($selector)->stack(); + break; + case 'slice': + // TODO jQuery difference ? + $args = explode( + ',', + str_replace(', ', ',', trim($args, "\"'")) + ); + $start = $args[0]; + $end = isset($args[1]) + ? $args[1] + : null; + if ($end > 0) + $end = $end - $start; + $this->elements = array_slice($this->elements, $start, $end); + break; + case 'has': + $selector = trim($args, "\"'"); + $stack = array(); + foreach ($this->stack(1) as $el) { + if ($this->find($selector, $el, true)->length) + $stack[] = $el; + } + $this->elements = $stack; + break; + case 'submit': + case 'reset': + $this->elements = phpQuery::merge( + $this->map( + array($this, 'is'), + "input[type=$class]", + new CallbackParam() + ), + $this->map( + array($this, 'is'), + "button[type=$class]", + new CallbackParam() + ) + ); + break; + // $stack = array(); + // foreach($this->elements as $node) + // if ($node->is('input[type=submit]') || $node->is('button[type=submit]')) + // $stack[] = $el; + // $this->elements = $stack; + case 'input': + $this->elements = $this->map( + array($this, 'is'), + 'input', + new CallbackParam() + )->elements; + break; + case 'password': + case 'checkbox': + case 'radio': + case 'hidden': + case 'image': + case 'file': + $this->elements = $this->map( + array($this, 'is'), + "input[type=$class]", + new CallbackParam() + )->elements; + break; + case 'parent': + $this->elements = $this->map( + function ($node) { + return $node instanceof DOMELEMENT && $node->childNodes->length + ? $node : null; + } + )->elements; + break; + case 'empty': + $this->elements = $this->map( + function ($node) { + return $node instanceof DOMELEMENT && $node->childNodes->length + ? null : $node; + } + )->elements; + break; + case 'disabled': + case 'selected': + case 'checked': + $this->elements = $this->map( + array($this, 'is'), + "[$class]", + new CallbackParam() + )->elements; + break; + case 'enabled': + $this->elements = $this->map( + function ($node) { + return pq($node)->not(":disabled") ? $node : null; + } + )->elements; + break; + case 'header': + $this->elements = $this->map( + function ($node) { + $isHeader = isset($node->tagName) && in_array($node->tagName, array( + "h1", "h2", "h3", "h4", "h5", "h6", "h7" + )); + return $isHeader + ? $node + : null; + } + )->elements; + // $this->elements = $this->map( + // create_function('$node', '$node = pq($node); + // return $node->is("h1") + // || $node->is("h2") + // || $node->is("h3") + // || $node->is("h4") + // || $node->is("h5") + // || $node->is("h6") + // || $node->is("h7") + // ? $node + // : null;') + // )->elements; + break; + case 'only-child': + $this->elements = $this->map( + function ($node) { + return pq($node)->siblings()->size() == 0 ? $node : null; + } + )->elements; + break; + case 'first-child': + $this->elements = $this->map( + function ($node) { + return pq($node)->prevAll()->size() == 0 ? $node : null; + } + )->elements; + break; + case 'last-child': + $this->elements = $this->map( + function ($node) { + return pq($node)->nextAll()->size() == 0 ? $node : null; + } + )->elements; + break; + case 'nth-child': + $param = trim($args, "\"'"); + if (!$param) + break; + // nth-child(n+b) to nth-child(1n+b) + if ($param[0] == 'n') + $param = '1' . $param; + // :nth-child(index/even/odd/equation) + if ($param == 'even' || $param == 'odd') + $mapped = $this->map( + function ($node, $param) { + $index = pq($node)->prevAll()->size() + 1; + if ($param == "even" && ($index % 2) == 0) + return $node; + else if ($param == "odd" && $index % 2 == 1) + return $node; + else + return null; + }, + new CallbackParam(), + $param + ); + else if (mb_strlen($param) > 1 && preg_match('/^(\d*)n([-+]?)(\d*)/', $param) === 1) + // an+b + $mapped = $this->map( + function ($node, $param) { + $prevs = pq($node)->prevAll()->size(); + $index = 1 + $prevs; + + preg_match("/^(\d*)n([-+]?)(\d*)/", $param, $matches); + $a = intval($matches[1]); + $b = intval($matches[3]); + if ($matches[2] === "-") { + $b = -$b; + } + + if ($a > 0) { + return ($index - $b) % $a == 0 + ? $node + : null; + phpQuery::debug($a . "*" . floor($index / $a) . "+$b-1 == " . ($a * floor($index / $a) + $b - 1) . " ?= $prevs"); + return $a * floor($index / $a) + $b - 1 == $prevs + ? $node + : null; + } else if ($a == 0) + return $index == $b + ? $node + : null; + else + // negative value + return $index <= $b + ? $node + : null; + // if (! $b) + // return $index%$a == 0 + // ? $node + // : null; + // else + // return ($index-$b)%$a == 0 + // ? $node + // : null; + }, + new CallbackParam(), + $param + ); + else + // index + $mapped = $this->map( + function ($node, $index) { + $prevs = pq($node)->prevAll()->size(); + if ($prevs && $prevs == $index - 1) + return $node; + else if (!$prevs && $index == 1) + return $node; + else + return null; + }, + new CallbackParam(), + $param + ); + $this->elements = $mapped->elements; + break; + default: + $this->debug("Unknown pseudoclass '{$class}', skipping..."); + } + } + /** + * @access private + */ + protected function __pseudoClassParam($paramsString) + { + // TODO; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function is($selector, $nodes = null) + { + phpQuery::debug(array("Is:", $selector)); + if (!$selector) + return false; + $oldStack = $this->elements; + $returnArray = false; + if ($nodes && is_array($nodes)) { + $this->elements = $nodes; + } else if ($nodes) + $this->elements = array($nodes); + $this->filter($selector, true); + $stack = $this->elements; + $this->elements = $oldStack; + if ($nodes) + return $stack ? $stack : null; + return (bool)count($stack); + } + /** + * Enter description here... + * jQuery difference. + * + * Callback: + * - $index int + * - $node DOMNode + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @link http://docs.jquery.com/Traversing/filter + */ + public function filterCallback($callback, $_skipHistory = false) + { + if (!$_skipHistory) { + $this->elementsBackup = $this->elements; + $this->debug("Filtering by callback"); + } + $newStack = array(); + foreach ($this->elements as $index => $node) { + $result = phpQuery::callbackRun($callback, array($index, $node)); + if (is_null($result) || (!is_null($result) && $result)) + $newStack[] = $node; + } + $this->elements = $newStack; + return $_skipHistory + ? $this + : $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @link http://docs.jquery.com/Traversing/filter + */ + public function filter($selectors, $_skipHistory = false) + { + if ($selectors instanceof Callback or $selectors instanceof Closure) + return $this->filterCallback($selectors, $_skipHistory); + if (!$_skipHistory) + $this->elementsBackup = $this->elements; + $notSimpleSelector = array(' ', '>', '~', '+', '/'); + if (!is_array($selectors)) + $selectors = $this->parseSelector($selectors); + if (!$_skipHistory) + $this->debug(array("Filtering:", $selectors)); + $finalStack = array(); + foreach ($selectors as $selector) { + $stack = array(); + if (!$selector) + break; + // avoid first space or / + if (in_array($selector[0], $notSimpleSelector)) + $selector = array_slice($selector, 1); + // PER NODE selector chunks + foreach ($this->stack() as $node) { + $break = false; + foreach ($selector as $s) { + if (!($node instanceof DOMELEMENT)) { + // all besides DOMElement + if ($s[0] == '[') { + $attr = trim($s, '[]'); + if (mb_strpos($attr, '=')) { + list($attr, $val) = explode('=', $attr); + if ($attr == 'nodeType' && $node->nodeType != $val) + $break = true; + } + } else + $break = true; + } else { + // DOMElement only + // ID + if ($s[0] == '#') { + if ($node->getAttribute('id') != substr($s, 1)) + $break = true; + // CLASSES + } else if ($s[0] == '.') { + if (!$this->matchClasses($s, $node)) + $break = true; + // ATTRS + } else if ($s[0] == '[') { + // strip side brackets + $attr = trim($s, '[]'); + if (mb_strpos($attr, '=')) { + list($attr, $val) = explode('=', $attr); + $val = self::unQuote($val); + if ($attr == 'nodeType') { + if ($val != $node->nodeType) + $break = true; + } else if ($this->isRegexp($attr)) { + $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport + ? quotemeta(trim($val, '"\'')) + : preg_quote(trim($val, '"\''), '@'); + // switch last character + switch (substr($attr, -1)) { + // quotemeta used insted of preg_quote + // http://code.google.com/p/phpquery/issues/detail?id=76 + case '^': + $pattern = '^' . $val; + break; + case '*': + $pattern = '.*' . $val . '.*'; + break; + case '$': + $pattern = '.*' . $val . '$'; + break; + } + // cut last character + $attr = substr($attr, 0, -1); + $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport + ? mb_ereg_match($pattern, $node->getAttribute($attr)) + : preg_match("@{$pattern}@", $node->getAttribute($attr)); + if (!$isMatch) + $break = true; + } else if ($node->getAttribute($attr) != $val) + $break = true; + } else if (!$node->hasAttribute($attr)) + $break = true; + // PSEUDO CLASSES + } else if ($s[0] == ':') { + // skip + // TAG + } else if (trim($s)) { + if ($s != '*') { + // TODO namespaces + if (isset($node->tagName)) { + if ($node->tagName != $s) + $break = true; + } else if ($s == 'html' && !$this->isRoot($node)) + $break = true; + } + // AVOID NON-SIMPLE SELECTORS + } else if (in_array($s, $notSimpleSelector)) { + $break = true; + $this->debug(array('Skipping non simple selector', $selector)); + } + } + if ($break) + break; + } + // if element passed all chunks of selector - add it to new stack + if (!$break) + $stack[] = $node; + } + $tmpStack = $this->elements; + $this->elements = $stack; + // PER ALL NODES selector chunks + foreach ($selector as $s) + // PSEUDO CLASSES + if ($s[0] == ':') + $this->pseudoClasses($s); + foreach ($this->elements as $node) + // XXX it should be merged without duplicates + // but jQuery doesnt do that + $finalStack[] = $node; + $this->elements = $tmpStack; + } + $this->elements = $finalStack; + if ($_skipHistory) { + return $this; + } else { + $this->debug("Stack length after filter(): " . count($finalStack)); + return $this->newInstance(); + } + } + /** + * + * @param $value + * @return unknown_type + * @TODO implement in all methods using passed parameters + */ + protected static function unQuote($value) + { + return $value[0] == '\'' || $value[0] == '"' + ? substr($value, 1, -1) + : $value; + } + /** + * Enter description here... + * + * @link http://docs.jquery.com/Ajax/load + * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo Support $selector + */ + public function load($url, $data = null, $callback = null) + { + if ($data && !is_array($data)) { + $callback = $data; + $data = null; + } + if (mb_strpos($url, ' ') !== false) { + $matches = null; + if (extension_loaded('mbstring') && phpQuery::$mbstringSupport) + mb_ereg('^([^ ]+) (.*)$', $url, $matches); + else + preg_match('^([^ ]+) (.*)$', $url, $matches); + $url = $matches[1]; + $selector = $matches[2]; + // FIXME this sucks, pass as callback param + $this->_loadSelector = $selector; + } + $ajax = array( + 'url' => $url, + 'type' => $data ? 'POST' : 'GET', + 'data' => $data, + 'complete' => $callback, + 'success' => array($this, '__loadSuccess') + ); + phpQuery::ajax($ajax); + return $this; + } + /** + * @access private + * @param $html + * @return unknown_type + */ + public function __loadSuccess($html) + { + if ($this->_loadSelector) { + $html = phpQuery::newDocument($html)->find($this->_loadSelector); + unset($this->_loadSelector); + } + foreach ($this->stack(1) as $node) { + phpQuery::pq($node, $this->getDocumentID()) + ->markup($html); + } + } + /** + * Enter description here... + * + * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo + */ + public function css() + { + // TODO + return $this; + } + /** + * @todo + * + */ + public function show() + { + // TODO + return $this; + } + /** + * @todo + * + */ + public function hide() + { + // TODO + return $this; + } + /** + * Trigger a type of event on every matched element. + * + * @param unknown_type $type + * @param unknown_type $data + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO support more than event in $type (space-separated) + */ + public function trigger($type, $data = array()) + { + foreach ($this->elements as $node) + phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node); + return $this; + } + /** + * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions. + * + * @param unknown_type $type + * @param unknown_type $data + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO + */ + public function triggerHandler($type, $data = array()) + { + // TODO; + } + /** + * Binds a handler to one or more events (like click) for each matched element. + * Can also bind custom events. + * + * @param unknown_type $type + * @param unknown_type $data Optional + * @param unknown_type $callback + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO support '!' (exclusive) events + * @TODO support more than event in $type (space-separated) + */ + public function bind($type, $data, $callback = null) + { + // TODO check if $data is callable, not using is_callable + if (!isset($callback)) { + $callback = $data; + $data = null; + } + foreach ($this->elements as $node) + phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback); + return $this; + } + /** + * Enter description here... + * + * @param unknown_type $type + * @param unknown_type $callback + * @return unknown + * @TODO namespace events + * @TODO support more than event in $type (space-separated) + */ + public function unbind($type = null, $callback = null) + { + foreach ($this->elements as $node) + phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function change($callback = null) + { + if ($callback) + return $this->bind('change', $callback); + return $this->trigger('change'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function submit($callback = null) + { + if ($callback) + return $this->bind('submit', $callback); + return $this->trigger('submit'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function click($callback = null) + { + if ($callback) + return $this->bind('click', $callback); + return $this->trigger('click'); + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapAllOld($wrapper) + { + $wrapper = pq($wrapper)->_clone(); + if (!$wrapper->length() || !$this->length()) + return $this; + $wrapper->insertBefore($this->elements[0]); + $deepest = $wrapper->elements[0]; + while ($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) + $deepest = $deepest->firstChild; + pq($deepest)->append($this); + return $this; + } + /** + * Enter description here... + * + * TODO testme... + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapAll($wrapper) + { + if (!$this->length()) + return $this; + return phpQuery::pq($wrapper, $this->getDocumentID()) + ->clone() + ->insertBefore($this->get(0)) + ->map(array($this, '___wrapAllCallback')) + ->append($this); + } + /** + * + * @param $node + * @return unknown_type + * @access private + */ + public function ___wrapAllCallback($node) + { + $deepest = $node; + while ($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) + $deepest = $deepest->firstChild; + return $deepest; + } + /** + * Enter description here... + * NON JQUERY METHOD + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapAllPHP($codeBefore, $codeAfter) + { + return $this + ->slice(0, 1) + ->beforePHP($codeBefore) + ->end() + ->slice(-1) + ->afterPHP($codeAfter) + ->end(); + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrap($wrapper) + { + foreach ($this->stack() as $node) + phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper); + return $this; + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapPHP($codeBefore, $codeAfter) + { + foreach ($this->stack() as $node) + phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter); + return $this; + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapInner($wrapper) + { + foreach ($this->stack() as $node) + phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper); + return $this; + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapInnerPHP($codeBefore, $codeAfter) + { + foreach ($this->stack(1) as $node) + phpQuery::pq($node, $this->getDocumentID())->contents() + ->wrapAllPHP($codeBefore, $codeAfter); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @testme Support for text nodes + */ + public function contents() + { + $stack = array(); + foreach ($this->stack(1) as $el) { + // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56 + // if (! isset($el->childNodes)) + // continue; + foreach ($el->childNodes as $node) { + $stack[] = $node; + } + } + return $this->newInstance($stack); + } + /** + * Enter description here... + * + * jQuery difference. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function contentsUnwrap() + { + foreach ($this->stack(1) as $node) { + if (!$node->parentNode) + continue; + $childNodes = array(); + // any modification in DOM tree breaks childNodes iteration, so cache them first + foreach ($node->childNodes as $chNode) + $childNodes[] = $chNode; + foreach ($childNodes as $chNode) + // $node->parentNode->appendChild($chNode); + $node->parentNode->insertBefore($chNode, $node); + $node->parentNode->removeChild($node); + } + return $this; + } + /** + * Enter description here... + * + * jQuery difference. + */ + public function switchWith($markup) + { + $markup = pq($markup, $this->getDocumentID()); + $content = null; + foreach ($this->stack(1) as $node) { + pq($node) + ->contents()->toReference($content)->end() + ->replaceWith($markup->clone()->append($content)); + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function eq($num) + { + $oldStack = $this->elements; + $this->elementsBackup = $this->elements; + $this->elements = array(); + if (isset($oldStack[$num])) + $this->elements[] = $oldStack[$num]; + return $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function size() + { + return count($this->elements); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @deprecated Use length as attribute + */ + public function length() + { + return $this->size(); + } + + #[\ReturnTypeWillChange] + public function count() + { + return $this->size(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo $level + */ + public function end($level = 1) + { + // $this->elements = array_pop( $this->history ); + // return $this; + // $this->previous->DOM = $this->DOM; + // $this->previous->XPath = $this->XPath; + return $this->previous + ? $this->previous + : $this; + } + /** + * Enter description here... + * Normal use ->clone() . + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function _clone() + { + $newStack = array(); + //pr(array('copy... ', $this->whois())); + //$this->dumpHistory('copy'); + $this->elementsBackup = $this->elements; + foreach ($this->elements as $node) { + $newStack[] = $node->cloneNode(true); + } + $this->elements = $newStack; + return $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function replaceWithPHP($code) + { + return $this->replaceWith(phpQuery::php($code)); + } + /** + * Enter description here... + * + * @param String|phpQuery $content + * @link http://docs.jquery.com/Manipulation/replaceWith#content + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function replaceWith($content) + { + return $this->after($content)->remove(); + } + /** + * Enter description here... + * + * @param String $selector + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo this works ? + */ + public function replaceAll($selector) + { + foreach (phpQuery::pq($selector, $this->getDocumentID()) as $node) + phpQuery::pq($node, $this->getDocumentID()) + ->after($this->_clone()) + ->remove(); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function remove($selector = null) + { + $loop = $selector + ? $this->filter($selector)->elements + : $this->elements; + foreach ($loop as $node) { + if (!$node->parentNode) + continue; + if (isset($node->tagName)) + $this->debug("Removing '{$node->tagName}'"); + $node->parentNode->removeChild($node); + // Mutation event + $event = new DOMEvent(array( + 'target' => $node, + 'type' => 'DOMNodeRemoved' + )); + phpQueryEvents::trigger( + $this->getDocumentID(), + $event->type, + array($event), + $node + ); + } + return $this; + } + protected function markupEvents($newMarkup, $oldMarkup, $node) + { + if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) { + $event = new DOMEvent(array( + 'target' => $node, + 'type' => 'change' + )); + phpQueryEvents::trigger( + $this->getDocumentID(), + $event->type, + array($event), + $node + ); + } + } + /** + * jQuey difference + * + * @param $markup + * @return unknown_type + * @TODO trigger change event for textarea + */ + public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) + { + $args = func_get_args(); + if ($this->documentWrapper->isXML) + return call_user_func_array(array($this, 'xml'), $args); + else + return call_user_func_array(array($this, 'html'), $args); + } + /** + * jQuey difference + * + * @param $markup + * @return unknown_type + */ + public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) + { + $args = func_get_args(); + if ($this->documentWrapper->isXML) + return call_user_func_array(array($this, 'xmlOuter'), $args); + else + return call_user_func_array(array($this, 'htmlOuter'), $args); + } + /** + * Enter description here... + * + * @param unknown_type $html + * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO force html result + */ + public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) + { + if (isset($html)) { + // INSERT + $nodes = $this->documentWrapper->import($html); + $this->empty(); + foreach ($this->stack(1) as $alreadyAdded => $node) { + // for now, limit events for textarea + if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') + $oldHtml = pq($node, $this->getDocumentID())->markup(); + foreach ($nodes as $newNode) { + $node->appendChild( + $alreadyAdded + ? $newNode->cloneNode(true) + : $newNode + ); + } + // for now, limit events for textarea + if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') + $this->markupEvents($html, $oldHtml, $node); + } + return $this; + } else { + // FETCH + $return = $this->documentWrapper->markup($this->elements, true); + $args = func_get_args(); + foreach (array_slice($args, 1) as $callback) { + $return = phpQuery::callbackRun($callback, array($return)); + } + return $return; + } + } + /** + * @TODO force xml result + */ + public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) + { + $args = func_get_args(); + return call_user_func_array(array($this, 'html'), $args); + } + /** + * Enter description here... + * @TODO force html result + * + * @return String + */ + public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) + { + $markup = $this->documentWrapper->markup($this->elements); + // pass thou callbacks + $args = func_get_args(); + foreach ($args as $callback) { + $markup = phpQuery::callbackRun($callback, array($markup)); + } + return $markup; + } + /** + * @TODO force xml result + */ + public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) + { + $args = func_get_args(); + return call_user_func_array(array($this, 'htmlOuter'), $args); + } + public function __toString() + { + return $this->markupOuter(); + } + /** + * Just like html(), but returns markup with VALID (dangerous) PHP tags. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo support returning markup with PHP tags when called without param + */ + public function php($code = null) + { + return $this->markupPHP($code); + } + /** + * Enter description here... + * + * @param $code + * @return unknown_type + */ + public function markupPHP($code = null) + { + return isset($code) + ? $this->markup(phpQuery::php($code)) + : phpQuery::markupToPHP($this->markup()); + } + /** + * Enter description here... + * + * @param $code + * @return unknown_type + */ + public function markupOuterPHP() + { + return phpQuery::markupToPHP($this->markupOuter()); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function children($selector = null) + { + $stack = array(); + foreach ($this->stack(1) as $node) { + // foreach($node->getElementsByTagName('*') as $newNode) { + foreach ($node->childNodes as $newNode) { + if ($newNode->nodeType != 1) + continue; + if ($selector && !$this->is($selector, $newNode)) + continue; + if ($this->elementsContainsNode($newNode, $stack)) + continue; + $stack[] = $newNode; + } + } + $this->elementsBackup = $this->elements; + $this->elements = $stack; + return $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function ancestors($selector = null) + { + return $this->children($selector); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function append($content) + { + return $this->insert($content, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function appendPHP($content) + { + return $this->insert("", 'append'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function appendTo($seletor) + { + return $this->insert($seletor, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function prepend($content) + { + return $this->insert($content, __FUNCTION__); + } + /** + * Enter description here... + * + * @todo accept many arguments, which are joined, arrays maybe also + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function prependPHP($content) + { + return $this->insert("", 'prepend'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function prependTo($seletor) + { + return $this->insert($seletor, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function before($content) + { + return $this->insert($content, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function beforePHP($content) + { + return $this->insert("", 'before'); + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function insertBefore($seletor) + { + return $this->insert($seletor, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function after($content) + { + return $this->insert($content, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function afterPHP($content) + { + return $this->insert("", 'after'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function insertAfter($seletor) + { + return $this->insert($seletor, __FUNCTION__); + } + /** + * Internal insert method. Don't use it. + * + * @param unknown_type $target + * @param unknown_type $type + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function insert($target, $type) + { + $this->debug("Inserting data with '{$type}'"); + $to = false; + switch ($type) { + case 'appendTo': + case 'prependTo': + case 'insertBefore': + case 'insertAfter': + $to = true; + } + switch (gettype($target)) { + case 'string': + $insertFrom = $insertTo = array(); + if ($to) { + // INSERT TO + $insertFrom = $this->elements; + if (phpQuery::isMarkup($target)) { + // $target is new markup, import it + $insertTo = $this->documentWrapper->import($target); + // insert into selected element + } else { + // $tagret is a selector + $thisStack = $this->elements; + $this->toRoot(); + $insertTo = $this->find($target)->elements; + $this->elements = $thisStack; + } + } else { + // INSERT FROM + $insertTo = $this->elements; + $insertFrom = $this->documentWrapper->import($target); + } + break; + case 'object': + $insertFrom = $insertTo = array(); + // phpQuery + if ($target instanceof self) { + if ($to) { + $insertTo = $target->elements; + if ($this->documentFragment && $this->stackIsRoot()) + // get all body children + // $loop = $this->find('body > *')->elements; + // TODO test it, test it hard... + // $loop = $this->newInstance($this->root)->find('> *')->elements; + $loop = $this->root->childNodes; + else + $loop = $this->elements; + // import nodes if needed + $insertFrom = $this->getDocumentID() == $target->getDocumentID() + ? $loop + : $target->documentWrapper->import($loop); + } else { + $insertTo = $this->elements; + if ($target->documentFragment && $target->stackIsRoot()) + // get all body children + // $loop = $target->find('body > *')->elements; + $loop = $target->root->childNodes; + else + $loop = $target->elements; + // import nodes if needed + $insertFrom = $this->getDocumentID() == $target->getDocumentID() + ? $loop + : $this->documentWrapper->import($loop); + } + // DOMNODE + } elseif ($target instanceof DOMNODE) { + // import node if needed + // if ( $target->ownerDocument != $this->DOM ) + // $target = $this->DOM->importNode($target, true); + if ($to) { + $insertTo = array($target); + if ($this->documentFragment && $this->stackIsRoot()) + // get all body children + $loop = $this->root->childNodes; + // $loop = $this->find('body > *')->elements; + else + $loop = $this->elements; + foreach ($loop as $fromNode) + // import nodes if needed + $insertFrom[] = !$fromNode->ownerDocument->isSameNode($target->ownerDocument) + ? $target->ownerDocument->importNode($fromNode, true) + : $fromNode; + } else { + // import node if needed + if (!$target->ownerDocument->isSameNode($this->document)) + $target = $this->document->importNode($target, true); + $insertTo = $this->elements; + $insertFrom[] = $target; + } + } + break; + } + phpQuery::debug("From " . count($insertFrom) . "; To " . count($insertTo) . " nodes"); + foreach ($insertTo as $insertNumber => $toNode) { + // we need static relative elements in some cases + switch ($type) { + case 'prependTo': + case 'prepend': + $firstChild = $toNode->firstChild; + break; + case 'insertAfter': + case 'after': + $nextSibling = $toNode->nextSibling; + break; + } + foreach ($insertFrom as $fromNode) { + // clone if inserted already before + $insert = $insertNumber + ? $fromNode->cloneNode(true) + : $fromNode; + switch ($type) { + case 'appendTo': + case 'append': + // $toNode->insertBefore( + // $fromNode, + // $toNode->lastChild->nextSibling + // ); + $toNode->appendChild($insert); + $eventTarget = $insert; + break; + case 'prependTo': + case 'prepend': + $toNode->insertBefore( + $insert, + $firstChild + ); + break; + case 'insertBefore': + case 'before': + if (!$toNode->parentNode) + throw new Exception("No parentNode, can't do {$type}()"); + else + $toNode->parentNode->insertBefore( + $insert, + $toNode + ); + break; + case 'insertAfter': + case 'after': + if (!$toNode->parentNode) + throw new Exception("No parentNode, can't do {$type}()"); + else + $toNode->parentNode->insertBefore( + $insert, + $nextSibling + ); + break; + } + // Mutation event + $event = new DOMEvent(array( + 'target' => $insert, + 'type' => 'DOMNodeInserted' + )); + phpQueryEvents::trigger( + $this->getDocumentID(), + $event->type, + array($event), + $insert + ); + } + } + return $this; + } + /** + * Enter description here... + * + * @return Int + */ + public function index($subject) + { + $index = -1; + $subject = $subject instanceof phpQueryObject + ? $subject->elements[0] + : $subject; + foreach ($this->newInstance() as $k => $node) { + if ($node->isSameNode($subject)) + $index = $k; + } + return $index; + } + /** + * Enter description here... + * + * @param unknown_type $start + * @param unknown_type $end + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @testme + */ + public function slice($start, $end = null) + { + // $last = count($this->elements)-1; + // $end = $end + // ? min($end, $last) + // : $last; + // if ($start < 0) + // $start = $last+$start; + // if ($start > $last) + // return array(); + if ($end > 0) + $end = $end - $start; + return $this->newInstance( + array_slice($this->elements, $start, $end) + ); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function reverse() + { + $this->elementsBackup = $this->elements; + $this->elements = array_reverse($this->elements); + return $this->newInstance(); + } + /** + * Return joined text content. + * @return String + */ + public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) + { + if (isset($text)) + return $this->html(htmlspecialchars($text)); + $args = func_get_args(); + $args = array_slice($args, 1); + $return = ''; + foreach ($this->elements as $node) { + $text = $node->textContent; + if (count($this->elements) > 1 && $text) + $text .= "\n"; + foreach ($args as $callback) { + $text = phpQuery::callbackRun($callback, array($text)); + } + $return .= $text; + } + return $return; + } + /** + * @return The text content of each matching element, like + * text() but returns an array with one entry per matched element. + * Read only. + */ + public function texts($attr = null) + { + $results = array(); + foreach ($this->elements as $node) { + $results[] = $node->textContent; + } + return $results; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function plugin($class, $file = null) + { + phpQuery::plugin($class, $file); + return $this; + } + /** + * Deprecated, use $pq->plugin() instead. + * + * @deprecated + * @param $class + * @param $file + * @return unknown_type + */ + public static function extend($class, $file = null) + { + return $this->plugin($class, $file); + } + /** + * + * @access private + * @param $method + * @param $args + * @return unknown_type + */ + public function __call($method, $args) + { + $aliasMethods = array('clone', 'empty'); + if (isset(phpQuery::$extendMethods[$method])) { + array_unshift($args, $this); + return phpQuery::callbackRun( + phpQuery::$extendMethods[$method], + $args + ); + } else if (isset(phpQuery::$pluginsMethods[$method])) { + array_unshift($args, $this); + $class = phpQuery::$pluginsMethods[$method]; + $realClass = "phpQueryObjectPlugin_$class"; + $return = call_user_func_array( + array($realClass, $method), + $args + ); + // XXX deprecate ? + return is_null($return) + ? $this + : $return; + } else if (in_array($method, $aliasMethods)) { + return call_user_func_array(array($this, '_' . $method), $args); + } else + throw new Exception("Method '{$method}' doesnt exist"); + } + /** + * Safe rename of next(). + * + * Use it ONLY when need to call next() on an iterated object (in same time). + * Normaly there is no need to do such thing ;) + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function _next($selector = null) + { + return $this->newInstance( + $this->getElementSiblings('nextSibling', $selector, true) + ); + } + /** + * Use prev() and next(). + * + * @deprecated + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function _prev($selector = null) + { + return $this->prev($selector); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function prev($selector = null) + { + return $this->newInstance( + $this->getElementSiblings('previousSibling', $selector, true) + ); + } + /** + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo + */ + public function prevAll($selector = null) + { + return $this->newInstance( + $this->getElementSiblings('previousSibling', $selector) + ); + } + /** + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo FIXME: returns source elements insted of next siblings + */ + public function nextAll($selector = null) + { + return $this->newInstance( + $this->getElementSiblings('nextSibling', $selector) + ); + } + /** + * @access private + */ + protected function getElementSiblings($direction, $selector = null, $limitToOne = false) + { + $stack = array(); + $count = 0; + foreach ($this->stack() as $node) { + $test = $node; + while (isset($test->{$direction}) && $test->{$direction}) { + $test = $test->{$direction}; + if (!$test instanceof DOMELEMENT) + continue; + $stack[] = $test; + if ($limitToOne) + break; + } + } + if ($selector) { + $stackOld = $this->elements; + $this->elements = $stack; + $stack = $this->filter($selector, true)->stack(); + $this->elements = $stackOld; + } + return $stack; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function siblings($selector = null) + { + $stack = array(); + $siblings = array_merge( + $this->getElementSiblings('previousSibling', $selector), + $this->getElementSiblings('nextSibling', $selector) + ); + foreach ($siblings as $node) { + if (!$this->elementsContainsNode($node, $stack)) + $stack[] = $node; + } + return $this->newInstance($stack); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function not($selector = null) + { + if (is_string($selector)) + phpQuery::debug(array('not', $selector)); + else + phpQuery::debug('not'); + $stack = array(); + if ($selector instanceof self || $selector instanceof DOMNODE) { + foreach ($this->stack() as $node) { + if ($selector instanceof self) { + $matchFound = false; + foreach ($selector->stack() as $notNode) { + if ($notNode->isSameNode($node)) + $matchFound = true; + } + if (!$matchFound) + $stack[] = $node; + } else if ($selector instanceof DOMNODE) { + if (!$selector->isSameNode($node)) + $stack[] = $node; + } else { + if (!$this->is($selector)) + $stack[] = $node; + } + } + } else { + $orgStack = $this->stack(); + $matched = $this->filter($selector, true)->stack(); + // $matched = array(); + // // simulate OR in filter() instead of AND 5y + // foreach($this->parseSelector($selector) as $s) { + // $matched = array_merge($matched, + // $this->filter(array($s))->stack() + // ); + // } + foreach ($orgStack as $node) + if (!$this->elementsContainsNode($node, $matched)) + $stack[] = $node; + } + return $this->newInstance($stack); + } + /** + * Enter description here... + * + * @param string|phpQueryObject + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function add($selector = null) + { + if (!$selector) + return $this; + $stack = array(); + $this->elementsBackup = $this->elements; + $found = phpQuery::pq($selector, $this->getDocumentID()); + $this->merge($found->elements); + return $this->newInstance(); + } + /** + * @access private + */ + protected function merge() + { + foreach (func_get_args() as $nodes) + foreach ($nodes as $newNode) + if (!$this->elementsContainsNode($newNode)) + $this->elements[] = $newNode; + } + /** + * @access private + * TODO refactor to stackContainsNode + */ + protected function elementsContainsNode($nodeToCheck, $elementsStack = null) + { + $loop = !is_null($elementsStack) + ? $elementsStack + : $this->elements; + foreach ($loop as $node) { + if ($node->isSameNode($nodeToCheck)) + return true; + } + return false; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function parent($selector = null) + { + $stack = array(); + foreach ($this->elements as $node) + if ($node->parentNode && !$this->elementsContainsNode($node->parentNode, $stack)) + $stack[] = $node->parentNode; + $this->elementsBackup = $this->elements; + $this->elements = $stack; + if ($selector) + $this->filter($selector, true); + return $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function parents($selector = null) + { + $stack = array(); + if (!$this->elements) + $this->debug('parents() - stack empty'); + foreach ($this->elements as $node) { + $test = $node; + while ($test->parentNode) { + $test = $test->parentNode; + if ($this->isRoot($test)) + break; + if (!$this->elementsContainsNode($test, $stack)) { + $stack[] = $test; + continue; + } + } + } + $this->elementsBackup = $this->elements; + $this->elements = $stack; + if ($selector) + $this->filter($selector, true); + return $this->newInstance(); + } + /** + * Internal stack iterator. + * + * @access private + */ + public function stack($nodeTypes = null) + { + if (!isset($nodeTypes)) + return $this->elements; + if (!is_array($nodeTypes)) + $nodeTypes = array($nodeTypes); + $return = array(); + foreach ($this->elements as $node) { + if (in_array($node->nodeType, $nodeTypes)) + $return[] = $node; + } + return $return; + } + // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes + protected function attrEvents($attr, $oldAttr, $oldValue, $node) + { + // skip events for XML documents + if (!$this->isXHTML() && !$this->isHTML()) + return; + $event = null; + // identify + $isInputValue = $node->tagName == 'input' + && (in_array( + $node->getAttribute('type'), + array('text', 'password', 'hidden') + ) + || !$node->getAttribute('type') + ); + $isRadio = $node->tagName == 'input' + && $node->getAttribute('type') == 'radio'; + $isCheckbox = $node->tagName == 'input' + && $node->getAttribute('type') == 'checkbox'; + $isOption = $node->tagName == 'option'; + if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) { + $event = new DOMEvent(array( + 'target' => $node, + 'type' => 'change' + )); + } else if (($isRadio || $isCheckbox) && $attr == 'checked' && ( + // check + (!$oldAttr && $node->hasAttribute($attr)) + // un-check + || (!$node->hasAttribute($attr) && $oldAttr) + )) { + $event = new DOMEvent(array( + 'target' => $node, + 'type' => 'change' + )); + } else if ($isOption && $node->parentNode && $attr == 'selected' && ( + // select + (!$oldAttr && $node->hasAttribute($attr)) + // un-select + || (!$node->hasAttribute($attr) && $oldAttr) + )) { + $event = new DOMEvent(array( + 'target' => $node->parentNode, + 'type' => 'change' + )); + } + if ($event) { + phpQueryEvents::trigger( + $this->getDocumentID(), + $event->type, + array($event), + $node + ); + } + } + public function attr($attr = null, $value = null) + { + foreach ($this->stack(1) as $node) { + if (!is_null($value)) { + $loop = $attr == '*' + ? $this->getNodeAttrs($node) + : array($attr); + foreach ($loop as $a) { + $oldValue = $node->getAttribute($a); + $oldAttr = $node->hasAttribute($a); + // TODO raises an error when charset other than UTF-8 + // while document's charset is also not UTF-8 + @$node->setAttribute($a, $value); + $this->attrEvents($a, $oldAttr, $oldValue, $node); + } + } else if ($attr == '*') { + // jQuery difference + $return = array(); + foreach ($node->attributes as $n => $v) + $return[$n] = $v->value; + return $return; + } else + return $node->hasAttribute($attr) + ? $node->getAttribute($attr) + : null; + } + return is_null($value) + ? '' : $this; + } + /** + * @return The same attribute of each matching element, like + * attr() but returns an array with one entry per matched element. + * Read only. + */ + public function attrs($attr = null) + { + $results = array(); + foreach ($this->stack(1) as $node) { + $results[] = $node->hasAttribute($attr) + ? $node->getAttribute($attr) + : null; + } + return $results; + } + /** + * @access private + */ + protected function getNodeAttrs($node) + { + $return = array(); + foreach ($node->attributes as $n => $o) + $return[] = $n; + return $return; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo check CDATA ??? + */ + public function attrPHP($attr, $code) + { + if (!is_null($code)) { + $value = '<' . '?php ' . $code . ' ?' . '>'; + // TODO tempolary solution + // http://code.google.com/p/phpquery/issues/detail?id=17 + // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII') + // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES'); + } + foreach ($this->stack(1) as $node) { + if (!is_null($code)) { + // $attrNode = $this->DOM->createAttribute($attr); + $node->setAttribute($attr, $value); + // $attrNode->value = $value; + // $node->appendChild($attrNode); + } else if ($attr == '*') { + // jQuery diff + $return = array(); + foreach ($node->attributes as $n => $v) + $return[$n] = $v->value; + return $return; + } else + return $node->getAttribute($attr); + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function removeAttr($attr) + { + foreach ($this->stack(1) as $node) { + $loop = $attr == '*' + ? $this->getNodeAttrs($node) + : array($attr); + foreach ($loop as $a) { + $oldValue = $node->getAttribute($a); + $node->removeAttribute($a); + $this->attrEvents($a, $oldValue, null, $node); + } + } + return $this; + } + /** + * Return form element value. + * + * @return String Fields value. + */ + public function val($val = null) + { + if (!isset($val)) { + if ($this->eq(0)->is('select')) { + $selected = $this->eq(0)->find('option[selected=selected]'); + if ($selected->is('[value]')) + return $selected->attr('value'); + else + return $selected->text(); + } else if ($this->eq(0)->is('textarea')) + return $this->eq(0)->markup(); + else + return $this->eq(0)->attr('value'); + } else { + $_val = null; + foreach ($this->stack(1) as $node) { + $node = pq($node, $this->getDocumentID()); + if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) { + $isChecked = in_array($node->attr('value'), $val) + || in_array($node->attr('name'), $val); + if ($isChecked) + $node->attr('checked', 'checked'); + else + $node->removeAttr('checked'); + } else if ($node->get(0)->tagName == 'select') { + if (!isset($_val)) { + $_val = array(); + if (!is_array($val)) + $_val = array((string)$val); + else + foreach ($val as $v) + $_val[] = $v; + } + foreach ($node['option']->stack(1) as $option) { + $option = pq($option, $this->getDocumentID()); + $selected = false; + // XXX: workaround for string comparsion, see issue #96 + // http://code.google.com/p/phpquery/issues/detail?id=96 + $selected = is_null($option->attr('value')) + ? in_array($option->markup(), $_val) + : in_array($option->attr('value'), $_val); + // $optionValue = $option->attr('value'); + // $optionText = $option->text(); + // $optionTextLenght = mb_strlen($optionText); + // foreach($_val as $v) + // if ($optionValue == $v) + // $selected = true; + // else if ($optionText == $v && $optionTextLenght == mb_strlen($v)) + // $selected = true; + if ($selected) + $option->attr('selected', 'selected'); + else + $option->removeAttr('selected'); + } + } else if ($node->get(0)->tagName == 'textarea') + $node->markup($val); + else + $node->attr('value', $val); + } + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function andSelf() + { + if ($this->previous) + $this->elements = array_merge($this->elements, $this->previous->elements); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function addClass($className) + { + if (!$className) + return $this; + foreach ($this->stack(1) as $node) { + if (!$this->is(".$className", $node)) + $node->setAttribute( + 'class', + trim($node->getAttribute('class') . ' ' . $className) + ); + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function addClassPHP($className) + { + foreach ($this->stack(1) as $node) { + $classes = $node->getAttribute('class'); + $newValue = $classes + ? $classes . ' <' . '?php ' . $className . ' ?' . '>' + : '<' . '?php ' . $className . ' ?' . '>'; + $node->setAttribute('class', $newValue); + } + return $this; + } + /** + * Enter description here... + * + * @param string $className + * @return bool + */ + public function hasClass($className) + { + foreach ($this->stack(1) as $node) { + if ($this->is(".$className", $node)) + return true; + } + return false; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function removeClass($className) + { + foreach ($this->stack(1) as $node) { + $classes = explode(' ', $node->getAttribute('class')); + if (in_array($className, $classes)) { + $classes = array_diff($classes, array($className)); + if ($classes) + $node->setAttribute('class', implode(' ', $classes)); + else + $node->removeAttribute('class'); + } + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function toggleClass($className) + { + foreach ($this->stack(1) as $node) { + if ($this->is($node, '.' . $className)) + $this->removeClass($className); + else + $this->addClass($className); + } + return $this; + } + /** + * Proper name without underscore (just ->empty()) also works. + * + * Removes all child nodes from the set of matched elements. + * + * Example: + * pq("p")._empty() + * + * HTML: + *

Hello, Person and person

+ * + * Result: + * [

] + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function _empty() + { + foreach ($this->stack(1) as $node) { + // thx to 'dave at dgx dot cz' + $node->nodeValue = ''; + } + return $this; + } + /** + * Enter description here... + * + * @param array|string $callback Expects $node as first param, $index as second + * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope) + * @param array $arg1 Will ba passed as third and futher args to callback. + * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on... + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function each($callback, $param1 = null, $param2 = null, $param3 = null) + { + $paramStructure = null; + if (func_num_args() > 1) { + $paramStructure = func_get_args(); + $paramStructure = array_slice($paramStructure, 1); + } + foreach ($this->elements as $v) + phpQuery::callbackRun($callback, array($v), $paramStructure); + return $this; + } + /** + * Run callback on actual object. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function callback($callback, $param1 = null, $param2 = null, $param3 = null) + { + $params = func_get_args(); + $params[0] = $this; + phpQuery::callbackRun($callback, $params); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo add $scope and $args as in each() ??? + */ + public function map($callback, $param1 = null, $param2 = null, $param3 = null) + { + // $stack = array(); + //// foreach($this->newInstance() as $node) { + // foreach($this->newInstance() as $node) { + // $result = call_user_func($callback, $node); + // if ($result) + // $stack[] = $result; + // } + $params = func_get_args(); + array_unshift($params, $this->elements); + return $this->newInstance( + call_user_func_array(array('phpQuery', 'map'), $params) + // phpQuery::map($this->elements, $callback) + ); + } + /** + * Enter description here... + * + * @param $key + * @param $value + */ + public function data($key, $value = null) + { + if (!isset($value)) { + // TODO? implement specific jQuery behavior od returning parent values + // is child which we look up doesn't exist + return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID()); + } else { + foreach ($this as $node) + phpQuery::data($node, $key, $value, $this->getDocumentID()); + return $this; + } + } + /** + * Enter description here... + * + * @param $key + */ + public function removeData($key) + { + foreach ($this as $node) + phpQuery::removeData($node, $key, $this->getDocumentID()); + return $this; + } + // INTERFACE IMPLEMENTATIONS + + // ITERATOR INTERFACE + /** + * @access private + */ + #[\ReturnTypeWillChange] + public function rewind() + { + $this->debug('iterating foreach'); + // phpQuery::selectDocument($this->getDocumentID()); + $this->elementsBackup = $this->elements; + $this->elementsInterator = $this->elements; + $this->valid = isset($this->elements[0]) + ? 1 : 0; + // $this->elements = $this->valid + // ? array($this->elements[0]) + // : array(); + $this->current = 0; + } + /** + * @access private + */ + #[\ReturnTypeWillChange] + public function current() + { + return $this->elementsInterator[$this->current]; + } + /** + * @access private + */ + #[\ReturnTypeWillChange] + public function key() + { + return $this->current; + } + /** + * Double-function method. + * + * First: main iterator interface method. + * Second: Returning next sibling, alias for _next(). + * + * Proper functionality is choosed automagicaly. + * + * @see phpQueryObject::_next() + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + #[\ReturnTypeWillChange] + public function next($cssSelector = null) + { + // if ($cssSelector || $this->valid) + // return $this->_next($cssSelector); + $this->valid = isset($this->elementsInterator[$this->current + 1]) + ? true + : false; + if (!$this->valid && $this->elementsInterator) { + $this->elementsInterator = null; + } else if ($this->valid) { + $this->current++; + } else { + return $this->_next($cssSelector); + } + } + /** + * @access private + */ + #[\ReturnTypeWillChange] + public function valid() + { + return $this->valid; + } + // ITERATOR INTERFACE END + // ARRAYACCESS INTERFACE + /** + * @access private + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return $this->find($offset)->size() > 0; + } + /** + * @access private + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->find($offset); + } + /** + * @access private + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + // $this->find($offset)->replaceWith($value); + $this->find($offset)->html($value); + } + /** + * @access private + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + // empty + throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML."); + } + // ARRAYACCESS INTERFACE END + /** + * Returns node's XPath. + * + * @param unknown_type $oneNode + * @return string + * @TODO use native getNodePath is avaible + * @access private + */ + protected function getNodeXpath($oneNode = null, $namespace = null) + { + $return = array(); + $loop = $oneNode + ? array($oneNode) + : $this->elements; + // if ($namespace) + // $namespace .= ':'; + foreach ($loop as $node) { + if ($node instanceof DOMDOCUMENT) { + $return[] = ''; + continue; + } + $xpath = array(); + while (!($node instanceof DOMDOCUMENT)) { + $i = 1; + $sibling = $node; + while ($sibling->previousSibling) { + $sibling = $sibling->previousSibling; + $isElement = $sibling instanceof DOMELEMENT; + if ($isElement && $sibling->tagName == $node->tagName) + $i++; + } + $xpath[] = $this->isXML() + ? "*[local-name()='{$node->tagName}'][{$i}]" + : "{$node->tagName}[{$i}]"; + $node = $node->parentNode; + } + $xpath = implode('/', array_reverse($xpath)); + $return[] = '/' . $xpath; + } + return $oneNode + ? $return[0] + : $return; + } + // HELPERS + public function whois($oneNode = null) + { + $return = array(); + $loop = $oneNode + ? array($oneNode) + : $this->elements; + foreach ($loop as $node) { + if (isset($node->tagName)) { + $tag = in_array($node->tagName, array('php', 'js')) + ? strtoupper($node->tagName) + : $node->tagName; + $return[] = $tag + . ($node->getAttribute('id') + ? '#' . $node->getAttribute('id') : '') + . ($node->getAttribute('class') + ? '.' . implode('.', explode(' ', $node->getAttribute('class'))) : '') + . ($node->getAttribute('name') + ? '[name="' . $node->getAttribute('name') . '"]' : '') + . ($node->getAttribute('value') && strpos($node->getAttribute('value'), '<' . '?php') === false + ? '[value="' . substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15) . '"]' : '') + . ($node->getAttribute('value') && strpos($node->getAttribute('value'), '<' . '?php') !== false + ? '[value=PHP]' : '') + . ($node->getAttribute('selected') + ? '[selected]' : '') + . ($node->getAttribute('checked') + ? '[checked]' : ''); + } else if ($node instanceof DOMTEXT) { + if (trim($node->textContent)) + $return[] = 'Text:' . substr(str_replace("\n", ' ', $node->textContent), 0, 15); + } else { + } + } + return $oneNode && isset($return[0]) + ? $return[0] + : $return; + } + /** + * Dump htmlOuter and preserve chain. Usefull for debugging. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * + */ + public function dump() + { + print 'DUMP #' . (phpQuery::$dumpCount++) . ' '; + $debug = phpQuery::$debug; + phpQuery::$debug = false; + // print __FILE__.':'.__LINE__."\n"; + var_dump($this->htmlOuter()); + return $this; + } + public function dumpWhois() + { + print 'DUMP #' . (phpQuery::$dumpCount++) . ' '; + $debug = phpQuery::$debug; + phpQuery::$debug = false; + // print __FILE__.':'.__LINE__."\n"; + var_dump('whois', $this->whois()); + phpQuery::$debug = $debug; + return $this; + } + public function dumpLength() + { + print 'DUMP #' . (phpQuery::$dumpCount++) . ' '; + $debug = phpQuery::$debug; + phpQuery::$debug = false; + // print __FILE__.':'.__LINE__."\n"; + var_dump('length', $this->length()); + phpQuery::$debug = $debug; + return $this; + } + public function dumpTree($html = true, $title = true) + { + $output = $title + ? 'DUMP #' . (phpQuery::$dumpCount++) . " \n" : ''; + $debug = phpQuery::$debug; + phpQuery::$debug = false; + foreach ($this->stack() as $node) + $output .= $this->__dumpTree($node); + phpQuery::$debug = $debug; + print $html + ? nl2br(str_replace(' ', ' ', $output)) + : $output; + return $this; + } + private function __dumpTree($node, $intend = 0) + { + $whois = $this->whois($node); + $return = ''; + if ($whois) + $return .= str_repeat(' - ', $intend) . $whois . "\n"; + if (isset($node->childNodes)) + foreach ($node->childNodes as $chNode) + $return .= $this->__dumpTree($chNode, $intend + 1); + return $return; + } + /** + * Dump htmlOuter and stop script execution. Usefull for debugging. + * + */ + public function dumpDie() + { + print __FILE__ . ':' . __LINE__; + var_dump($this->htmlOuter()); + die(); + } +} + + +// -- Multibyte Compatibility functions --------------------------------------- +// http://svn.iphonewebdev.com/lace/lib/mb_compat.php + +/** + * mb_internal_encoding() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($enc) + { + return true; + } +} + +/** + * mb_regex_encoding() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_regex_encoding')) { + function mb_regex_encoding($enc) + { + return true; + } +} + +/** + * mb_strlen() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_strlen')) { + function mb_strlen($str) + { + return strlen($str); + } +} + +/** + * mb_strpos() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0) + { + return strpos($haystack, $needle, $offset); + } +} +/** + * mb_stripos() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0) + { + return stripos($haystack, $needle, $offset); + } +} + +/** + * mb_substr() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_substr')) { + function mb_substr($str, $start, $length = 0) + { + return substr($str, $start, $length); + } +} + +/** + * mb_substr_count() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle) + { + return substr_count($haystack, $needle); + } +} + + +/** + * Static namespace for phpQuery functions. + * + * @author Tobiasz Cudnik + * @package phpQuery + */ +abstract class phpQuery +{ + /** + * XXX: Workaround for mbstring problems + * + * @var bool + */ + public static $mbstringSupport = true; + public static $debug = false; + public static $documents = array(); + public static $defaultDocumentID = null; + // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'; + /** + * Applies only to HTML. + * + * @var unknown_type + */ + public static $defaultDoctype = ''; + public static $defaultCharset = 'UTF-8'; + /** + * Static namespace for plugins. + * + * @var object + */ + public static $plugins = array(); + /** + * List of loaded plugins. + * + * @var unknown_type + */ + public static $pluginsLoaded = array(); + public static $pluginsMethods = array(); + public static $pluginsStaticMethods = array(); + public static $extendMethods = array(); + /** + * @TODO implement + */ + public static $extendStaticMethods = array(); + /** + * Hosts allowed for AJAX connections. + * Dot '.' means $_SERVER['HTTP_HOST'] (if any). + * + * @var array + */ + public static $ajaxAllowedHosts = array( + '.' + ); + /** + * AJAX settings. + * + * @var array + * XXX should it be static or not ? + */ + public static $ajaxSettings = array( + 'url' => '', //TODO + 'global' => true, + 'type' => "GET", + 'timeout' => null, + 'contentType' => "application/x-www-form-urlencoded", + 'processData' => true, + // 'async' => true, + 'data' => null, + 'username' => null, + 'password' => null, + 'accepts' => array( + 'xml' => "application/xml, text/xml", + 'html' => "text/html", + 'script' => "text/javascript, application/javascript", + 'json' => "application/json, text/javascript", + 'text' => "text/plain", + '_default' => "*/*" + ) + ); + public static $lastModified = null; + public static $active = 0; + public static $dumpCount = 0; + /** + * Multi-purpose function. + * Use pq() as shortcut. + * + * In below examples, $pq is any result of pq(); function. + * + * 1. Import markup into existing document (without any attaching): + * - Import into selected document: + * pq('
') // DOESNT accept text nodes at beginning of input string ! + * - Import into document with ID from $pq->getDocumentID(): + * pq('
', $pq->getDocumentID()) + * - Import into same document as DOMNode belongs to: + * pq('
', DOMNode) + * - Import into document from phpQuery object: + * pq('
', $pq) + * + * 2. Run query: + * - Run query on last selected document: + * pq('div.myClass') + * - Run query on document with ID from $pq->getDocumentID(): + * pq('div.myClass', $pq->getDocumentID()) + * - Run query on same document as DOMNode belongs to and use node(s)as root for query: + * pq('div.myClass', DOMNode) + * - Run query on document from phpQuery object + * and use object's stack as root node(s) for query: + * pq('div.myClass', $pq) + * + * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes + * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root) + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false + * phpQuery object or false in case of error. + */ + public static function pq($arg1, $context = null) + { + if ($arg1 instanceof DOMNODE && !isset($context)) { + foreach (phpQuery::$documents as $documentWrapper) { + $compare = $arg1 instanceof DOMDocument + ? $arg1 : $arg1->ownerDocument; + if ($documentWrapper->document->isSameNode($compare)) + $context = $documentWrapper->id; + } + } + if (!$context) { + $domId = self::$defaultDocumentID; + if (!$domId) + throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first."); + // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) + } else if (is_object($context) && $context instanceof phpQueryObject) + $domId = $context->getDocumentID(); + else if ($context instanceof DOMDOCUMENT) { + $domId = self::getDocumentID($context); + if (!$domId) { + //throw new Exception('Orphaned DOMDocument'); + $domId = self::newDocument($context)->getDocumentID(); + } + } else if ($context instanceof DOMNODE) { + $domId = self::getDocumentID($context); + if (!$domId) { + throw new Exception('Orphaned DOMNode'); + // $domId = self::newDocument($context->ownerDocument); + } + } else + $domId = $context; + if ($arg1 instanceof phpQueryObject) { + // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) { + /** + * Return $arg1 or import $arg1 stack if document differs: + * pq(pq('
')) + */ + if ($arg1->getDocumentID() == $domId) + return $arg1; + $class = get_class($arg1); + // support inheritance by passing old object to overloaded constructor + $phpQuery = $class != 'phpQuery' + ? new $class($arg1, $domId) + : new phpQueryObject($domId); + $phpQuery->elements = array(); + foreach ($arg1->elements as $node) + $phpQuery->elements[] = $phpQuery->document->importNode($node, true); + return $phpQuery; + } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) { + /* + * Wrap DOM nodes with phpQuery object, import into document when needed: + * pq(array($domNode1, $domNode2)) + */ + $phpQuery = new phpQueryObject($domId); + if (!($arg1 instanceof DOMNODELIST) && !is_array($arg1)) + $arg1 = array($arg1); + $phpQuery->elements = array(); + foreach ($arg1 as $node) { + $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT + && !$node->ownerDocument->isSameNode($phpQuery->document); + $phpQuery->elements[] = $sameDocument + ? $phpQuery->document->importNode($node, true) + : $node; + } + return $phpQuery; + } else if (self::isMarkup($arg1)) { + /** + * Import HTML: + * pq('
') + */ + $phpQuery = new phpQueryObject($domId); + return $phpQuery->newInstance( + $phpQuery->documentWrapper->import($arg1) + ); + } else { + /** + * Run CSS query: + * pq('div.myClass') + */ + $phpQuery = new phpQueryObject($domId); + // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) + if ($context && $context instanceof phpQueryObject) + $phpQuery->elements = $context->elements; + else if ($context && $context instanceof DOMNODELIST) { + $phpQuery->elements = array(); + foreach ($context as $node) + $phpQuery->elements[] = $node; + } else if ($context && $context instanceof DOMNODE) + $phpQuery->elements = array($context); + return $phpQuery->find($arg1); + } + } + /** + * Sets default document to $id. Document has to be loaded prior + * to using this method. + * $id can be retrived via getDocumentID() or getDocumentIDRef(). + * + * @param unknown_type $id + */ + public static function selectDocument($id) + { + $id = self::getDocumentID($id); + self::debug("Selecting document '$id' as default one"); + self::$defaultDocumentID = self::getDocumentID($id); + } + /** + * Returns document with id $id or last used as phpQueryObject. + * $id can be retrived via getDocumentID() or getDocumentIDRef(). + * Chainable. + * + * @see phpQuery::selectDocument() + * @param unknown_type $id + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function getDocument($id = null) + { + if ($id) + phpQuery::selectDocument($id); + else + $id = phpQuery::$defaultDocumentID; + return new phpQueryObject($id); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocument($markup = null, $contentType = null) + { + if (!$markup) + $markup = ''; + $documentID = phpQuery::createDocumentWrapper($markup, $contentType); + return new phpQueryObject($documentID); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentHTML($markup = null, $charset = null) + { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocument($markup, "text/html{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentXML($markup = null, $charset = null) + { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocument($markup, "text/xml{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentXHTML($markup = null, $charset = null) + { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocument($markup, "application/xhtml+xml{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentPHP($markup = null, $contentType = "text/html") + { + // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function) + $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset); + return self::newDocument($markup, $contentType); + } + public static function phpToMarkup($php, $charset = 'utf-8') + { + $regexes = array( + '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<' . '?php?(.*?)(?:\\?>)([^\']*)\'@s', + '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<' . '?php?(.*?)(?:\\?>)([^"]*)"@s', + ); + foreach ($regexes as $regex) + while (preg_match($regex, $php, $matches)) { + $php = preg_replace_callback( + $regex, + // create_function('$m, $charset = "'.$charset.'"', + // 'return $m[1].$m[2] + // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) + // .$m[5].$m[2];' + // ), + array('phpQuery', '_phpToMarkupCallback'), + $php + ); + } + $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s'; + //preg_match_all($regex, $php, $matches); + //var_dump($matches); + $php = preg_replace($regex, '\\1', $php); + return $php; + } + public static function _phpToMarkupCallback($php, $charset = 'utf-8') + { + return $m[1] . $m[2] + . htmlspecialchars("<" . "?php" . $m[4] . "?" . ">", ENT_QUOTES | ENT_NOQUOTES, $charset) + . $m[5] . $m[2]; + } + public static function _markupToPHPCallback($m) + { + return "<" . "?php " . htmlspecialchars_decode($m[1]) . " ?" . ">"; + } + /** + * Converts document markup containing PHP code generated by phpQuery::php() + * into valid (executable) PHP code syntax. + * + * @param string|phpQueryObject $content + * @return string PHP code. + */ + public static function markupToPHP($content) + { + if ($content instanceof phpQueryObject) + $content = $content->markupOuter(); + /* ... to */ + $content = preg_replace_callback( + '@\s*\s*@s', + // create_function('$m', + // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";' + // ), + array('phpQuery', '_markupToPHPCallback'), + $content + ); + /* extra space added to save highlighters */ + $regexes = array( + '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^\']*)\'@s', + '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^"]*)"@s', + ); + foreach ($regexes as $regex) + while (preg_match($regex, $content)) + $content = preg_replace_callback( + $regex, + function ($m) { + return $m[1] . $m[2] . $m[3] . "", " ", "\n", " ", "{", "$", "}", '"', "[", "]"), + htmlspecialchars_decode($m[4]) + ) + . " ?>" . $m[5] . $m[2]; + }, + $content + ); + return $content; + } + /** + * Creates new document from file $file. + * Chainable. + * + * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources. + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFile($file, $contentType = null) + { + $documentID = self::createDocumentWrapper( + file_get_contents($file), + $contentType + ); + return new phpQueryObject($documentID); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFileHTML($file, $charset = null) + { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocumentFile($file, "text/html{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFileXML($file, $charset = null) + { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocumentFile($file, "text/xml{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFileXHTML($file, $charset = null) + { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocumentFile($file, "application/xhtml+xml{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFilePHP($file, $contentType = null) + { + return self::newDocumentPHP(file_get_contents($file), $contentType); + } + /** + * Reuses existing DOMDocument object. + * Chainable. + * + * @param $document DOMDocument + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO support DOMDocument + */ + public static function loadDocument($document) + { + // TODO + die('TODO loadDocument'); + } + /** + * Enter description here... + * + * @param unknown_type $html + * @param unknown_type $domId + * @return unknown New DOM ID + * @todo support PHP tags in input + * @todo support passing DOMDocument object from self::loadDocument + */ + protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) + { + if (function_exists('domxml_open_mem')) + throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled."); + // $id = $documentID + // ? $documentID + // : md5(microtime()); + $document = null; + if ($html instanceof DOMDOCUMENT) { + if (self::getDocumentID($html)) { + // document already exists in phpQuery::$documents, make a copy + $document = clone $html; + } else { + // new document, add it to phpQuery::$documents + $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); + } + } else { + $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); + } + // $wrapper->id = $id; + // bind document + phpQuery::$documents[$wrapper->id] = $wrapper; + // remember last loaded document + phpQuery::selectDocument($wrapper->id); + return $wrapper->id; + } + /** + * Extend class namespace. + * + * @param string|array $target + * @param array $source + * @TODO support string $source + * @return unknown_type + */ + public static function extend($target, $source) + { + switch ($target) { + case 'phpQueryObject': + $targetRef = &self::$extendMethods; + $targetRef2 = &self::$pluginsMethods; + break; + case 'phpQuery': + $targetRef = &self::$extendStaticMethods; + $targetRef2 = &self::$pluginsStaticMethods; + break; + default: + throw new Exception("Unsupported \$target type"); + } + if (is_string($source)) + $source = array($source => $source); + foreach ($source as $method => $callback) { + if (isset($targetRef[$method])) { + // throw new Exception + self::debug("Duplicate method '{$method}', can\'t extend '{$target}'"); + continue; + } + if (isset($targetRef2[$method])) { + // throw new Exception + self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}'," + . " can\'t extend '{$target}'"); + continue; + } + $targetRef[$method] = $callback; + } + return true; + } + /** + * Extend phpQuery with $class from $file. + * + * @param string $class Extending class name. Real class name can be prepended phpQuery_. + * @param string $file Filename to include. Defaults to "{$class}.php". + */ + public static function plugin($class, $file = null) + { + // TODO $class checked agains phpQuery_$class + // if (strpos($class, 'phpQuery') === 0) + // $class = substr($class, 8); + if (in_array($class, self::$pluginsLoaded)) + return true; + if (!$file) + $file = $class . '.php'; + $objectClassExists = class_exists('phpQueryObjectPlugin_' . $class); + $staticClassExists = class_exists('phpQueryPlugin_' . $class); + if (!$objectClassExists && !$staticClassExists) + require_once($file); + self::$pluginsLoaded[] = $class; + // static methods + if (class_exists('phpQueryPlugin_' . $class)) { + $realClass = 'phpQueryPlugin_' . $class; + $vars = get_class_vars($realClass); + $loop = isset($vars['phpQueryMethods']) + && !is_null($vars['phpQueryMethods']) + ? $vars['phpQueryMethods'] + : get_class_methods($realClass); + foreach ($loop as $method) { + if ($method == '__initialize') + continue; + if (!is_callable(array($realClass, $method))) + continue; + if (isset(self::$pluginsStaticMethods[$method])) { + throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '" . self::$pluginsStaticMethods[$method] . "'"); + return; + } + self::$pluginsStaticMethods[$method] = $class; + } + if (method_exists($realClass, '__initialize')) + call_user_func_array(array($realClass, '__initialize'), array()); + } + // object methods + if (class_exists('phpQueryObjectPlugin_' . $class)) { + $realClass = 'phpQueryObjectPlugin_' . $class; + $vars = get_class_vars($realClass); + $loop = isset($vars['phpQueryMethods']) + && !is_null($vars['phpQueryMethods']) + ? $vars['phpQueryMethods'] + : get_class_methods($realClass); + foreach ($loop as $method) { + if (!is_callable(array($realClass, $method))) + continue; + if (isset(self::$pluginsMethods[$method])) { + throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '" . self::$pluginsMethods[$method] . "'"); + continue; + } + self::$pluginsMethods[$method] = $class; + } + } + return true; + } + /** + * Unloades all or specified document from memory. + * + * @param mixed $documentID @see phpQuery::getDocumentID() for supported types. + */ + public static function unloadDocuments($id = null) + { + if (isset($id)) { + if ($id = self::getDocumentID($id)) + unset(phpQuery::$documents[$id]); + } else { + foreach (phpQuery::$documents as $k => $v) { + unset(phpQuery::$documents[$k]); + } + } + } + /** + * Parses phpQuery object or HTML result against PHP tags and makes them active. + * + * @param phpQuery|string $content + * @deprecated + * @return string + */ + public static function unsafePHPTags($content) + { + return self::markupToPHP($content); + } + public static function DOMNodeListToArray($DOMNodeList) + { + $array = array(); + if (!$DOMNodeList) + return $array; + foreach ($DOMNodeList as $node) + $array[] = $node; + return $array; + } + /** + * Checks if $input is HTML string, which has to start with '<'. + * + * @deprecated + * @param String $input + * @return Bool + * @todo still used ? + */ + public static function isMarkup($input) + { + return !is_array($input) && substr(trim($input), 0, 1) == '<'; + } + public static function debug($text) + { + if (self::$debug) + print var_dump($text); + } + /** + * Make an AJAX request. + * + * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions + * Additional options are: + * 'document' - document for global events, @see phpQuery::getDocumentID() + * 'referer' - implemented + * 'requested_with' - TODO; not implemented (X-Requested-With) + * @return Zend_Http_Client + * @link http://docs.jquery.com/Ajax/jQuery.ajax + * + * @TODO $options['cache'] + * @TODO $options['processData'] + * @TODO $options['xhr'] + * @TODO $options['data'] as string + * @TODO XHR interface + */ + public static function ajax($options = array(), $xhr = null) + { + $options = array_merge( + self::$ajaxSettings, + $options + ); + $documentID = isset($options['document']) + ? self::getDocumentID($options['document']) + : null; + if ($xhr) { + // reuse existing XHR object, but clean it up + $client = $xhr; + // $client->setParameterPost(null); + // $client->setParameterGet(null); + $client->setAuth(false); + $client->setHeaders("If-Modified-Since", null); + $client->setHeaders("Referer", null); + $client->resetParameters(); + } else { + // create new XHR object + require_once('Zend/Http/Client.php'); + $client = new Zend_Http_Client(); + $client->setCookieJar(); + } + if (isset($options['timeout'])) + $client->setConfig(array( + 'timeout' => $options['timeout'], + )); + // 'maxredirects' => 0, + foreach (self::$ajaxAllowedHosts as $k => $host) + if ($host == '.' && isset($_SERVER['HTTP_HOST'])) + self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST']; + $host = parse_url($options['url'], PHP_URL_HOST); + if (!in_array($host, self::$ajaxAllowedHosts)) { + throw new Exception("Request not permitted, host '$host' not present in " + . "phpQuery::\$ajaxAllowedHosts"); + } + // JSONP + $jsre = "/=\\?(&|$)/"; + if (isset($options['dataType']) && $options['dataType'] == 'jsonp') { + $jsonpCallbackParam = $options['jsonp'] + ? $options['jsonp'] : 'callback'; + if (strtolower($options['type']) == 'get') { + if (!preg_match($jsre, $options['url'])) { + $sep = strpos($options['url'], '?') + ? '&' : '?'; + $options['url'] .= "$sep$jsonpCallbackParam=?"; + } + } else if ($options['data']) { + $jsonp = false; + foreach ($options['data'] as $n => $v) { + if ($v == '?') + $jsonp = true; + } + if (!$jsonp) { + $options['data'][$jsonpCallbackParam] = '?'; + } + } + $options['dataType'] = 'json'; + } + if (isset($options['dataType']) && $options['dataType'] == 'json') { + $jsonpCallback = 'json_' . md5(microtime()); + $jsonpData = $jsonpUrl = false; + if ($options['data']) { + foreach ($options['data'] as $n => $v) { + if ($v == '?') + $jsonpData = $n; + } + } + if (preg_match($jsre, $options['url'])) + $jsonpUrl = true; + if ($jsonpData !== false || $jsonpUrl) { + // remember callback name for httpData() + $options['_jsonp'] = $jsonpCallback; + if ($jsonpData !== false) + $options['data'][$jsonpData] = $jsonpCallback; + if ($jsonpUrl) + $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']); + } + } + $client->setUri($options['url']); + $client->setMethod(strtoupper($options['type'])); + if (isset($options['referer']) && $options['referer']) + $client->setHeaders('Referer', $options['referer']); + $client->setHeaders(array( + // 'content-type' => $options['contentType'], + 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko' + . '/2008122010 Firefox/3.0.5', + // TODO custom charset + 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + // 'Connection' => 'keep-alive', + // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language' => 'en-us,en;q=0.5', + )); + if ($options['username']) + $client->setAuth($options['username'], $options['password']); + if (isset($options['ifModified']) && $options['ifModified']) + $client->setHeaders( + "If-Modified-Since", + self::$lastModified + ? self::$lastModified + : "Thu, 01 Jan 1970 00:00:00 GMT" + ); + $client->setHeaders( + "Accept", + isset($options['dataType']) + && isset(self::$ajaxSettings['accepts'][$options['dataType']]) + ? self::$ajaxSettings['accepts'][$options['dataType']] . ", */*" + : self::$ajaxSettings['accepts']['_default'] + ); + // TODO $options['processData'] + if ($options['data'] instanceof phpQueryObject) { + $serialized = $options['data']->serializeArray($options['data']); + $options['data'] = array(); + foreach ($serialized as $r) + $options['data'][$r['name']] = $r['value']; + } + if (strtolower($options['type']) == 'get') { + $client->setParameterGet($options['data']); + } else if (strtolower($options['type']) == 'post') { + $client->setEncType($options['contentType']); + $client->setParameterPost($options['data']); + } + if (self::$active == 0 && $options['global']) + phpQueryEvents::trigger($documentID, 'ajaxStart'); + self::$active++; + // beforeSend callback + if (isset($options['beforeSend']) && $options['beforeSend']) + phpQuery::callbackRun($options['beforeSend'], array($client)); + // ajaxSend event + if ($options['global']) + phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options)); + if (phpQuery::$debug) { + self::debug("{$options['type']}: {$options['url']}\n"); + self::debug("Options:
" . var_export($options, true) . "
\n"); + // if ($client->getCookieJar()) + // self::debug("Cookies:
".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."
\n"); + } + // request + $response = $client->request(); + if (phpQuery::$debug) { + self::debug('Status: ' . $response->getStatus() . ' / ' . $response->getMessage()); + self::debug($client->getLastRequest()); + self::debug($response->getHeaders()); + } + if ($response->isSuccessful()) { + // XXX tempolary + self::$lastModified = $response->getHeader('Last-Modified'); + $data = self::httpData($response->getBody(), $options['dataType'], $options); + if (isset($options['success']) && $options['success']) + phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options)); + if ($options['global']) + phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options)); + } else { + if (isset($options['error']) && $options['error']) + phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage())); + if ($options['global']) + phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/ $response->getMessage(), $options)); + } + if (isset($options['complete']) && $options['complete']) + phpQuery::callbackRun($options['complete'], array($client, $response->getStatus())); + if ($options['global']) + phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options)); + if ($options['global'] && !--self::$active) + phpQueryEvents::trigger($documentID, 'ajaxStop'); + return $client; + // if (is_null($domId)) + // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false; + // return new phpQueryAjaxResponse($response, $domId); + } + protected static function httpData($data, $type, $options) + { + if (isset($options['dataFilter']) && $options['dataFilter']) + $data = self::callbackRun($options['dataFilter'], array($data, $type)); + if (is_string($data)) { + if ($type == "json") { + if (isset($options['_jsonp']) && $options['_jsonp']) { + $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data); + } + $data = self::parseJSON($data); + } + } + return $data; + } + /** + * Enter description here... + * + * @param array|phpQuery $data + * + */ + public static function param($data) + { + return http_build_query($data, null, '&'); + } + public static function get($url, $data = null, $callback = null, $type = null) + { + if (!is_array($data)) { + $callback = $data; + $data = null; + } + // TODO some array_values on this shit + return phpQuery::ajax(array( + 'type' => 'GET', + 'url' => $url, + 'data' => $data, + 'success' => $callback, + 'dataType' => $type, + )); + } + public static function post($url, $data = null, $callback = null, $type = null) + { + if (!is_array($data)) { + $callback = $data; + $data = null; + } + return phpQuery::ajax(array( + 'type' => 'POST', + 'url' => $url, + 'data' => $data, + 'success' => $callback, + 'dataType' => $type, + )); + } + public static function getJSON($url, $data = null, $callback = null) + { + if (!is_array($data)) { + $callback = $data; + $data = null; + } + // TODO some array_values on this shit + return phpQuery::ajax(array( + 'type' => 'GET', + 'url' => $url, + 'data' => $data, + 'success' => $callback, + 'dataType' => 'json', + )); + } + public static function ajaxSetup($options) + { + self::$ajaxSettings = array_merge( + self::$ajaxSettings, + $options + ); + } + public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) + { + $loop = is_array($host1) + ? $host1 + : func_get_args(); + foreach ($loop as $host) { + if ($host && !in_array($host, phpQuery::$ajaxAllowedHosts)) { + phpQuery::$ajaxAllowedHosts[] = $host; + } + } + } + public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) + { + $loop = is_array($url1) + ? $url1 + : func_get_args(); + foreach ($loop as $url) + phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST)); + } + /** + * Returns JSON representation of $data. + * + * @static + * @param mixed $data + * @return string + */ + public static function toJSON($data) + { + if (function_exists('json_encode')) + return json_encode($data); + require_once('Zend/Json/Encoder.php'); + return Zend_Json_Encoder::encode($data); + } + /** + * Parses JSON into proper PHP type. + * + * @static + * @param string $json + * @return mixed + */ + public static function parseJSON($json) + { + if (function_exists('json_decode')) { + $return = json_decode(trim($json), true); + // json_decode and UTF8 issues + if (isset($return)) + return $return; + } + require_once('Zend/Json/Decoder.php'); + return Zend_Json_Decoder::decode($json); + } + /** + * Returns source's document ID. + * + * @param $source DOMNode|phpQueryObject + * @return string + */ + public static function getDocumentID($source) + { + if ($source instanceof DOMDOCUMENT) { + foreach (phpQuery::$documents as $id => $document) { + if ($source->isSameNode($document->document)) + return $id; + } + } else if ($source instanceof DOMNODE) { + foreach (phpQuery::$documents as $id => $document) { + if ($source->ownerDocument->isSameNode($document->document)) + return $id; + } + } else if ($source instanceof phpQueryObject) + return $source->getDocumentID(); + else if (is_string($source) && isset(phpQuery::$documents[$source])) + return $source; + } + /** + * Get DOMDocument object related to $source. + * Returns null if such document doesn't exist. + * + * @param $source DOMNode|phpQueryObject|string + * @return string + */ + public static function getDOMDocument($source) + { + if ($source instanceof DOMDOCUMENT) + return $source; + $source = self::getDocumentID($source); + return $source + ? self::$documents[$id]['document'] + : null; + } + + // UTILITIES + // http://docs.jquery.com/Utilities + + /** + * + * @return unknown_type + * @link http://docs.jquery.com/Utilities/jQuery.makeArray + */ + public static function makeArray($obj) + { + $array = array(); + if (is_object($object) && $object instanceof DOMNODELIST) { + foreach ($object as $value) + $array[] = $value; + } else if (is_object($object) && !($object instanceof Iterator)) { + foreach (get_object_vars($object) as $name => $value) + $array[0][$name] = $value; + } else { + foreach ($object as $name => $value) + $array[0][$name] = $value; + } + return $array; + } + public static function inArray($value, $array) + { + return in_array($value, $array); + } + /** + * + * @param $object + * @param $callback + * @return unknown_type + * @link http://docs.jquery.com/Utilities/jQuery.each + */ + public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) + { + $paramStructure = null; + if (func_num_args() > 2) { + $paramStructure = func_get_args(); + $paramStructure = array_slice($paramStructure, 2); + } + if (is_object($object) && !($object instanceof Iterator)) { + foreach (get_object_vars($object) as $name => $value) + phpQuery::callbackRun($callback, array($name, $value), $paramStructure); + } else { + foreach ($object as $name => $value) + phpQuery::callbackRun($callback, array($name, $value), $paramStructure); + } + } + /** + * + * @link http://docs.jquery.com/Utilities/jQuery.map + */ + public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) + { + $result = array(); + $paramStructure = null; + if (func_num_args() > 2) { + $paramStructure = func_get_args(); + $paramStructure = array_slice($paramStructure, 2); + } + foreach ($array as $v) { + $vv = phpQuery::callbackRun($callback, array($v), $paramStructure); + // $callbackArgs = $args; + // foreach($args as $i => $arg) { + // $callbackArgs[$i] = $arg instanceof CallbackParam + // ? $v + // : $arg; + // } + // $vv = call_user_func_array($callback, $callbackArgs); + if (is_array($vv)) { + foreach ($vv as $vvv) + $result[] = $vvv; + } else if ($vv !== null) { + $result[] = $vv; + } + } + return $result; + } + /** + * + * @param $callback Callback + * @param $params + * @param $paramStructure + * @return unknown_type + */ + public static function callbackRun($callback, $params = array(), $paramStructure = null) + { + if (!$callback) + return; + if ($callback instanceof CallbackParameterToReference) { + // TODO support ParamStructure to select which $param push to reference + if (isset($params[0])) + $callback->callback = $params[0]; + return true; + } + if ($callback instanceof Callback) { + $paramStructure = $callback->params; + $callback = $callback->callback; + } + if (!$paramStructure) + return call_user_func_array($callback, $params); + $p = 0; + foreach ($paramStructure as $i => $v) { + $paramStructure[$i] = $v instanceof CallbackParam + ? $params[$p++] + : $v; + } + return call_user_func_array($callback, $paramStructure); + } + /** + * Merge 2 phpQuery objects. + * @param array $one + * @param array $two + * @protected + * @todo node lists, phpQueryObject + */ + public static function merge($one, $two) + { + $elements = $one->elements; + foreach ($two->elements as $node) { + $exists = false; + foreach ($elements as $node2) { + if ($node2->isSameNode($node)) + $exists = true; + } + if (!$exists) + $elements[] = $node; + } + return $elements; + // $one = $one->newInstance(); + // $one->elements = $elements; + // return $one; + } + /** + * + * @param $array + * @param $callback + * @param $invert + * @return unknown_type + * @link http://docs.jquery.com/Utilities/jQuery.grep + */ + public static function grep($array, $callback, $invert = false) + { + $result = array(); + foreach ($array as $k => $v) { + $r = call_user_func_array($callback, array($v, $k)); + if ($r === !(bool)$invert) + $result[] = $v; + } + return $result; + } + public static function unique($array) + { + return array_unique($array); + } + /** + * + * @param $function + * @return unknown_type + * @TODO there are problems with non-static methods, second parameter pass it + * but doesnt verify is method is really callable + */ + public static function isFunction($function) + { + return is_callable($function); + } + public static function trim($str) + { + return trim($str); + } + /* PLUGINS NAMESPACE */ + /** + * + * @param $url + * @param $callback + * @param $param1 + * @param $param2 + * @param $param3 + * @return phpQueryObject + */ + public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) + { + if (self::plugin('WebBrowser')) { + $params = func_get_args(); + return self::callbackRun(array(self::$plugins, 'browserGet'), $params); + } else { + self::debug('WebBrowser plugin not available...'); + } + } + /** + * + * @param $url + * @param $data + * @param $callback + * @param $param1 + * @param $param2 + * @param $param3 + * @return phpQueryObject + */ + public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) + { + if (self::plugin('WebBrowser')) { + $params = func_get_args(); + return self::callbackRun(array(self::$plugins, 'browserPost'), $params); + } else { + self::debug('WebBrowser plugin not available...'); + } + } + /** + * + * @param $ajaxSettings + * @param $callback + * @param $param1 + * @param $param2 + * @param $param3 + * @return phpQueryObject + */ + public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) + { + if (self::plugin('WebBrowser')) { + $params = func_get_args(); + return self::callbackRun(array(self::$plugins, 'browser'), $params); + } else { + self::debug('WebBrowser plugin not available...'); + } + } + /** + * + * @param $code + * @return string + */ + public static function php($code) + { + return self::code('php', $code); + } + /** + * + * @param $type + * @param $code + * @return string + */ + public static function code($type, $code) + { + return "<$type>"; + } + + public static function __callStatic($method, $params) + { + return call_user_func_array( + array(phpQuery::$plugins, $method), + $params + ); + } + protected static function dataSetupNode($node, $documentID) + { + // search are return if alredy exists + foreach (phpQuery::$documents[$documentID]->dataNodes as $dataNode) { + if ($node->isSameNode($dataNode)) + return $dataNode; + } + // if doesn't, add it + phpQuery::$documents[$documentID]->dataNodes[] = $node; + return $node; + } + protected static function dataRemoveNode($node, $documentID) + { + // search are return if alredy exists + foreach (phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) { + if ($node->isSameNode($dataNode)) { + unset(self::$documents[$documentID]->dataNodes[$k]); + unset(self::$documents[$documentID]->data[$dataNode->dataID]); + } + } + } + public static function data($node, $name, $data, $documentID = null) + { + if (!$documentID) + // TODO check if this works + $documentID = self::getDocumentID($node); + $document = phpQuery::$documents[$documentID]; + $node = self::dataSetupNode($node, $documentID); + if (!isset($node->dataID)) + $node->dataID = ++phpQuery::$documents[$documentID]->uuid; + $id = $node->dataID; + if (!isset($document->data[$id])) + $document->data[$id] = array(); + if (!is_null($data)) + $document->data[$id][$name] = $data; + if ($name) { + if (isset($document->data[$id][$name])) + return $document->data[$id][$name]; + } else + return $id; + } + public static function removeData($node, $name, $documentID) + { + if (!$documentID) + // TODO check if this works + $documentID = self::getDocumentID($node); + $document = phpQuery::$documents[$documentID]; + $node = self::dataSetupNode($node, $documentID); + $id = $node->dataID; + if ($name) { + if (isset($document->data[$id][$name])) + unset($document->data[$id][$name]); + $name = null; + foreach ($document->data[$id] as $name) + break; + if (!$name) + self::removeData($node, $name, $documentID); + } else { + self::dataRemoveNode($node, $documentID); + } + } +} +/** + * Plugins static namespace class. + * + * @author Tobiasz Cudnik + * @package phpQuery + * @todo move plugin methods here (as statics) + */ +class phpQueryPlugins +{ + public function __call($method, $args) + { + if (isset(phpQuery::$extendStaticMethods[$method])) { + $return = call_user_func_array( + phpQuery::$extendStaticMethods[$method], + $args + ); + } else if (isset(phpQuery::$pluginsStaticMethods[$method])) { + $class = phpQuery::$pluginsStaticMethods[$method]; + $realClass = "phpQueryPlugin_$class"; + $return = call_user_func_array( + array($realClass, $method), + $args + ); + return isset($return) + ? $return + : $this; + } else + throw new Exception("Method '{$method}' doesnt exist"); + } +} +/** + * Shortcut to phpQuery::pq($arg1, $context) + * Chainable. + * + * @see phpQuery::pq() + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @author Tobiasz Cudnik + * @package phpQuery + */ +function pq($arg1, $context = null) +{ + $args = func_get_args(); + return call_user_func_array( + array('phpQuery', 'pq'), + $args + ); +} +// add plugins dir and Zend framework to include path +set_include_path( + get_include_path() + . PATH_SEPARATOR . dirname(__FILE__) . '/phpQuery/' + . PATH_SEPARATOR . dirname(__FILE__) . '/phpQuery/plugins/' +); +// why ? no __call nor __get for statics in php... +// XXX __callStatic will be available in PHP 5.3 +phpQuery::$plugins = new phpQueryPlugins(); +// include bootstrap file (personal library config) +if (file_exists(dirname(__FILE__) . '/phpQuery/bootstrap.php')) + require_once dirname(__FILE__) . '/phpQuery/bootstrap.php'; diff --git a/vendor/jaeger/querylist/.github/FUNDING.yml b/vendor/jaeger/querylist/.github/FUNDING.yml new file mode 100644 index 0000000..c53fd16 --- /dev/null +++ b/vendor/jaeger/querylist/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: querylist # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/vendor/jaeger/querylist/.gitignore b/vendor/jaeger/querylist/.gitignore new file mode 100644 index 0000000..e2ee195 --- /dev/null +++ b/vendor/jaeger/querylist/.gitignore @@ -0,0 +1,5 @@ +/vendor/ +.idea/ +composer.lock +.DS_Store +*.cache \ No newline at end of file diff --git a/vendor/jaeger/querylist/README-ZH.md b/vendor/jaeger/querylist/README-ZH.md new file mode 100644 index 0000000..ec34971 --- /dev/null +++ b/vendor/jaeger/querylist/README-ZH.md @@ -0,0 +1,309 @@ +

+ QueryList +
+
+

+ +# QueryList 简介 +`QueryList`是一套简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。 + +## 特性 +- 拥有与jQuery完全相同的CSS3 DOM选择器 +- 拥有与jQuery完全相同的DOM操作API +- 拥有通用的列表采集方案 +- 拥有强大的HTTP请求套件,轻松实现如:模拟登陆、伪造浏览器、HTTP代理等意复杂的网络请求 +- 拥有乱码解决方案 +- 拥有强大的内容过滤功能,可使用jQuey选择器来过滤内容 +- 拥有高度的模块化设计,扩展性强 +- 拥有富有表现力的API +- 拥有高质量文档 +- 拥有丰富的插件 +- 拥有专业的问答社区和交流群 + +通过插件可以轻松实现诸如: +- 多线程采集 +- 采集JavaScript动态渲染的页面 (PhantomJS/headless WebKit) +- 图片本地化 +- 模拟浏览器行为,如:提交Form表单 +- 网络爬虫 +- ..... + +## 环境要求 +- PHP >= 7.1 + +> 如果你的PHP版本还停留在PHP5,或者不会使用Composer,你可以选择使用QueryList3,QueryList3支持php5.3以及手动安装。 +QueryList3 文档:http://v3.querylist.cc + +## 安装 +通过Composer安装: +``` +composer require jaeger/querylist +``` + +## 使用 + +#### 元素操作 +- 采集「昵图网」所有图片地址 + +```php +QueryList::get('http://www.nipic.com')->find('img')->attrs('src'); +``` +- 采集百度搜索结果 + +```php +$ql = QueryList::get('http://www.baidu.com/s?wd=QueryList'); + +$ql->find('title')->text(); // 获取网站标题 +$ql->find('meta[name=keywords]')->content; // 获取网站头部关键词 + +$ql->find('h3>a')->texts(); //获取搜索结果标题列表 +$ql->find('h3>a')->attrs('href'); //获取搜索结果链接列表 + +$ql->find('img')->src; //获取第一张图片的链接地址 +$ql->find('img:eq(1)')->src; //获取第二张图片的链接地址 +$ql->find('img')->eq(2)->src; //获取第三张图片的链接地址 +// 遍历所有图片 +$ql->find('img')->map(function($img){ + echo $img->alt; //打印图片的alt属性 +}); +``` +- 更多用法 + +```php +$ql->find('#head')->append('
追加内容
')->find('div')->htmls(); +$ql->find('.two')->children('img')->attrs('alt'); //获取class为two元素下的所有img孩子节点 +//遍历class为two元素下的所有孩子节点 +$data = $ql->find('.two')->children()->map(function ($item){ + //用is判断节点类型 + if($item->is('a')){ + return $item->text(); + }elseif($item->is('img')) + { + return $item->alt; + } +}); + +$ql->find('a')->attr('href', 'newVal')->removeClass('className')->html('newHtml')->... +$ql->find('div > p')->add('div > ul')->filter(':has(a)')->find('p:first')->nextAll()->andSelf()->... +$ql->find('div.old')->replaceWith( $ql->find('div.new')->clone())->appendTo('.trash')->prepend('Deleted')->... +``` +#### 列表采集 +采集百度搜索结果列表的标题和链接: +```php +$data = QueryList::get('http://www.baidu.com/s?wd=QueryList') + // 设置采集规则 + ->rules([ + 'title'=>array('h3','text'), + 'link'=>array('h3>a','href') + ]) + ->query()->getData(); + +print_r($data->all()); +``` +采集结果: +``` +Array +( + [0] => Array + ( + [title] => QueryList|基于phpQuery的无比强大的PHP采集工具 + [link] => http://www.baidu.com/link?url=GU_YbDT2IHk4ns1tjG2I8_vjmH0SCJEAPuuZN + ) + [1] => Array + ( + [title] => PHP 用QueryList抓取网页内容 - wb145230 - 博客园 + [link] => http://www.baidu.com/link?url=zn0DXBnrvIF2ibRVW34KcRVFG1_bCdZvqvwIhUqiXaS + ) + [2] => Array + ( + [title] => 介绍- QueryList指导文档 + [link] => http://www.baidu.com/link?url=pSypvMovqS4v2sWeQo5fDBJ4EoYhXYi0Lxx + ) + //... +) +``` +#### 编码转换 +```php +// 输出编码:UTF-8,输入编码:GB2312 +QueryList::get('https://top.etao.com')->encoding('UTF-8','GB2312')->find('a')->texts(); + +// 输出编码:UTF-8,输入编码:自动识别 +QueryList::get('https://top.etao.com')->encoding('UTF-8')->find('a')->texts(); +``` + +#### HTTP网络操作(GuzzleHttp) +- 携带cookie登录新浪微博 +```php +//采集新浪微博需要登录才能访问的页面 +$ql = QueryList::get('http://weibo.com','param1=testvalue & params2=somevalue',[ + 'headers' => [ + //填写从浏览器获取到的cookie + 'Cookie' => 'SINAGLOBAL=546064; wb_cmtLike_2112031=1; wvr=6;....' + ] +]); +//echo $ql->getHtml(); +echo $ql->find('title')->text(); +//输出: 我的首页 微博-随时随地发现新鲜事 +``` +- 使用Http代理 +```php +$urlParams = ['param1' => 'testvalue','params2' => 'somevalue']; +$opts = [ + // 设置http代理 + 'proxy' => 'http://222.141.11.17:8118', + //设置超时时间,单位:秒 + 'timeout' => 30, + // 伪造http头 + 'headers' => [ + 'Referer' => 'https://querylist.cc/', + 'User-Agent' => 'testing/1.0', + 'Accept' => 'application/json', + 'X-Foo' => ['Bar', 'Baz'], + 'Cookie' => 'abc=111;xxx=222' + ] +]; +$ql->get('http://httpbin.org/get',$urlParams,$opts); +// echo $ql->getHtml(); +``` + +- 模拟登录 +```php +// 用post登录 +$ql = QueryList::post('http://xxxx.com/login',[ + 'username' => 'admin', + 'password' => '123456' +])->get('http://xxx.com/admin'); +//采集需要登录才能访问的页面 +$ql->get('http://xxx.com/admin/page'); +//echo $ql->getHtml(); +``` + +#### Form表单操作 +模拟登陆GitHub +```php +// 获取QueryList实例 +$ql = QueryList::getInstance(); +//获取到登录表单 +$form = $ql->get('https://github.com/login')->find('form'); + +//填写GitHub用户名和密码 +$form->find('input[name=login]')->val('your github username or email'); +$form->find('input[name=password]')->val('your github password'); + +//序列化表单数据 +$fromData = $form->serializeArray(); +$postData = []; +foreach ($fromData as $item) { + $postData[$item['name']] = $item['value']; +} + +//提交登录表单 +$actionUrl = 'https://github.com'.$form->attr('action'); +$ql->post($actionUrl,$postData); +//判断登录是否成功 +// echo $ql->getHtml(); +$userName = $ql->find('.header-nav-current-user>.css-truncate-target')->text(); +if($userName) +{ + echo '登录成功!欢迎你:'.$userName; +}else{ + echo '登录失败!'; +} +``` +#### Bind功能扩展 +自定义扩展一个`myHttp`方法: +```php +$ql = QueryList::getInstance(); + +//绑定一个myHttp方法到QueryList对象 +$ql->bind('myHttp',function ($url){ + // $this 为当前的QueryList对象 + $html = file_get_contents($url); + $this->setHtml($html); + return $this; +}); + +//然后就可以通过注册的名字来调用 +$data = $ql->myHttp('https://toutiao.io')->find('h3 a')->texts(); +print_r($data->all()); +``` +或者把实现体封装到class,然后这样绑定: +```php +$ql->bind('myHttp',function ($url){ + return new MyHttp($this,$url); +}); +``` + +#### 插件使用 +- 使用PhantomJS插件采集JavaScript动态渲染的页面: + +```php +// 安装时设置PhantomJS二进制文件路径 +$ql = QueryList::use(PhantomJs::class,'/usr/local/bin/phantomjs'); + +// 采集今日头条手机版 +$data = $ql->browser('https://m.toutiao.com')->find('p')->texts(); +print_r($data->all()); + +// 使用HTTP代理 +$ql->browser('https://m.toutiao.com',false,[ + '--proxy' => '192.168.1.42:8080', + '--proxy-type' => 'http' +]) +``` + +- 使用CURL多线程插件,多线程采集GitHub排行榜: + +```php +$ql = QueryList::use(CurlMulti::class); +$ql->curlMulti([ + 'https://github.com/trending/php', + 'https://github.com/trending/go', + //.....more urls +]) + // 每个任务成功完成调用此回调 + ->success(function (QueryList $ql,CurlMulti $curl,$r){ + echo "Current url:{$r['info']['url']} \r\n"; + $data = $ql->find('h3 a')->texts(); + print_r($data->all()); +}) + // 每个任务失败回调 +->error(function ($errorInfo,CurlMulti $curl){ + echo "Current url:{$errorInfo['info']['url']} \r\n"; + print_r($errorInfo['error']); +}) +->start([ + // 最大并发数 + 'maxThread' => 10, + // 错误重试次数 + 'maxTry' => 3, +]); + +``` + +## 插件 +- [jae-jae/QueryList-PhantomJS](https://github.com/jae-jae/QueryList-PhantomJS): 使用PhantomJS采集JavaScript动态渲染的页面 +- [jae-jae/QueryList-CurlMulti](https://github.com/jae-jae/QueryList-CurlMulti) : Curl多线程采集 +- [jae-jae/QueryList-AbsoluteUrl](https://github.com/jae-jae/QueryList-AbsoluteUrl) : 转换URL相对路径到绝对路径 +- [jae-jae/QueryList-Rule-Google](https://github.com/jae-jae/QueryList-Rule-Google) : 谷歌搜索引擎 +- [jae-jae/QueryList-Rule-Baidu](https://github.com/jae-jae/QueryList-Rule-Baidu) : 百度搜索引擎 + + +查看更多的QueryList插件和基于QueryList的产品:[QueryList社区力量](https://github.com/jae-jae/QueryList-Community) + +## 贡献 +欢迎为QueryList贡献代码。关于贡献插件可以查看:[QueryList插件贡献说明](https://github.com/jae-jae/QueryList-Community/blob/master/CONTRIBUTING.md) + +## 寻求帮助? +- QueryList主页: [http://querylist.cc](http://querylist.cc/) +- QueryList文档: [http://doc.querylist.cc](http://doc.querylist.cc/) +- QueryList问答:[http://wenda.querylist.cc](http://wenda.querylist.cc/) +- QueryList交流QQ群:123266961 cafeEX +- GitHub:https://github.com/jae-jae/QueryList +- Git@OSC:http://git.oschina.net/jae/QueryList + +## Author +Jaeger + +## Lisence +QueryList is licensed under the license of MIT. See the LICENSE for more details. diff --git a/vendor/jaeger/querylist/README.md b/vendor/jaeger/querylist/README.md new file mode 100644 index 0000000..5ec559f --- /dev/null +++ b/vendor/jaeger/querylist/README.md @@ -0,0 +1,304 @@ +

+ QueryList +
+
+

+ +# QueryList +`QueryList` is a simple, elegant, extensible PHP Web Scraper (crawler/spider) ,based on phpQuery. + +[API Documentation](https://github.com/jae-jae/QueryList/wiki) + +[中文文档](README-ZH.md) + +## Features +- Have the same CSS3 DOM selector as jQuery +- Have the same DOM manipulation API as jQuery +- Have a generic list crawling program +- Have a strong HTTP request suite, easy to achieve such as: simulated landing, forged browser, HTTP proxy and other complex network requests +- Have a messy code solution +- Have powerful content filtering, you can use the jQuey selector to filter content +- Has a high degree of modular design, scalability and strong +- Have an expressive API +- Has a wealth of plug-ins + +Through plug-ins you can easily implement things like: +- Multithreaded crawl +- Crawl JavaScript dynamic rendering page (PhantomJS/headless WebKit) +- Image downloads to local +- Simulate browser behavior such as submitting Form forms +- Web crawler +- ..... + +## Requirements +- PHP >= 7.1 + +## Installation +By Composer installation: +``` +composer require jaeger/querylist +``` + +## Usage + +#### DOM Traversal and Manipulation +- Crawl「GitHub」all picture links + +```php +QueryList::get('https://github.com')->find('img')->attrs('src'); +``` +- Crawl Google search results + +```php +$ql = QueryList::get('https://www.google.co.jp/search?q=QueryList'); + +$ql->find('title')->text(); //The page title +$ql->find('meta[name=keywords]')->content; //The page keywords + +$ql->find('h3>a')->texts(); //Get a list of search results titles +$ql->find('h3>a')->attrs('href'); //Get a list of search results links + +$ql->find('img')->src; //Gets the link address of the first image +$ql->find('img:eq(1)')->src; //Gets the link address of the second image +$ql->find('img')->eq(2)->src; //Gets the link address of the third image +// Loop all the images +$ql->find('img')->map(function($img){ + echo $img->alt; //Print the alt attribute of the image +}); +``` +- More usage + +```php +$ql->find('#head')->append('
Append content
')->find('div')->htmls(); +$ql->find('.two')->children('img')->attrs('alt'); // Get the class is the "two" element under all img child nodes +// Loop class is the "two" element under all child nodes +$data = $ql->find('.two')->children()->map(function ($item){ + // Use "is" to determine the node type + if($item->is('a')){ + return $item->text(); + }elseif($item->is('img')) + { + return $item->alt; + } +}); + +$ql->find('a')->attr('href', 'newVal')->removeClass('className')->html('newHtml')->... +$ql->find('div > p')->add('div > ul')->filter(':has(a)')->find('p:first')->nextAll()->andSelf()->... +$ql->find('div.old')->replaceWith( $ql->find('div.new')->clone())->appendTo('.trash')->prepend('Deleted')->... +``` +#### List crawl +Crawl the title and link of the Google search results list: +```php +$data = QueryList::get('https://www.google.co.jp/search?q=QueryList') + // Set the crawl rules + ->rules([ + 'title'=>array('h3','text'), + 'link'=>array('h3>a','href') + ]) + ->query()->getData(); + +print_r($data->all()); +``` + Results: +``` +Array +( + [0] => Array + ( + [title] => Angular - QueryList + [link] => https://angular.io/api/core/QueryList + ) + [1] => Array + ( + [title] => QueryList | @angular/core - Angularリファレンス - Web Creative Park + [link] => http://www.webcreativepark.net/angular/querylist/ + ) + [2] => Array + ( + [title] => QueryListにQueryを追加したり、追加されたことを感知する | TIPS ... + [link] => http://www.webcreativepark.net/angular/querylist_query_add_subscribe/ + ) + //... +) +``` +#### Encode convert +```php +// Out charset :UTF-8 +// In charset :GB2312 +QueryList::get('https://top.etao.com')->encoding('UTF-8','GB2312')->find('a')->texts(); + +// Out charset:UTF-8 +// In charset:Automatic Identification +QueryList::get('https://top.etao.com')->encoding('UTF-8')->find('a')->texts(); +``` + +#### HTTP Client (GuzzleHttp) +- Carry cookie login GitHub +```php +//Crawl GitHub content +$ql = QueryList::get('https://github.com','param1=testvalue & params2=somevalue',[ + 'headers' => [ + // Fill in the cookie from the browser + 'Cookie' => 'SINAGLOBAL=546064; wb_cmtLike_2112031=1; wvr=6;....' + ] +]); +//echo $ql->getHtml(); +$userName = $ql->find('.header-nav-current-user>.css-truncate-target')->text(); +echo $userName; +``` +- Use the Http proxy +```php +$urlParams = ['param1' => 'testvalue','params2' => 'somevalue']; +$opts = [ + // Set the http proxy + 'proxy' => 'http://222.141.11.17:8118', + //Set the timeout time in seconds + 'timeout' => 30, + // Fake HTTP headers + 'headers' => [ + 'Referer' => 'https://querylist.cc/', + 'User-Agent' => 'testing/1.0', + 'Accept' => 'application/json', + 'X-Foo' => ['Bar', 'Baz'], + 'Cookie' => 'abc=111;xxx=222' + ] +]; +$ql->get('http://httpbin.org/get',$urlParams,$opts); +// echo $ql->getHtml(); +``` + +- Analog login +```php +// Post login +$ql = QueryList::post('http://xxxx.com/login',[ + 'username' => 'admin', + 'password' => '123456' +])->get('http://xxx.com/admin'); +// Crawl pages that need to be logged in to access +$ql->get('http://xxx.com/admin/page'); +//echo $ql->getHtml(); +``` + +#### Submit forms +Login GitHub +```php +// Get the QueryList instance +$ql = QueryList::getInstance(); +// Get the login form +$form = $ql->get('https://github.com/login')->find('form'); + +// Fill in the GitHub username and password +$form->find('input[name=login]')->val('your github username or email'); +$form->find('input[name=password]')->val('your github password'); + +// Serialize the form data +$fromData = $form->serializeArray(); +$postData = []; +foreach ($fromData as $item) { + $postData[$item['name']] = $item['value']; +} + +// Submit the login form +$actionUrl = 'https://github.com'.$form->attr('action'); +$ql->post($actionUrl,$postData); +// To determine whether the login is successful +// echo $ql->getHtml(); +$userName = $ql->find('.header-nav-current-user>.css-truncate-target')->text(); +if($userName) +{ + echo 'Login successful ! Welcome:'.$userName; +}else{ + echo 'Login failed !'; +} +``` +#### Bind function extension +Customize the extension of a `myHttp` method: +```php +$ql = QueryList::getInstance(); + +//Bind a `myHttp` method to the QueryList object +$ql->bind('myHttp',function ($url){ + // $this is the current QueryList object + $html = file_get_contents($url); + $this->setHtml($html); + return $this; +}); + +// And then you can call by the name of the binding +$data = $ql->myHttp('https://toutiao.io')->find('h3 a')->texts(); +print_r($data->all()); +``` +Or package to class, and then bind: +```php +$ql->bind('myHttp',function ($url){ + return new MyHttp($this,$url); +}); +``` + +#### Plugin used +- Use the PhantomJS plugin to crawl JavaScript dynamically rendered pages: + +```php +// Set the PhantomJS binary file path during installation +$ql = QueryList::use(PhantomJs::class,'/usr/local/bin/phantomjs'); + +// Crawl「500px」all picture links +$data = $ql->browser('https://500px.com/editors')->find('img')->attrs('src'); +print_r($data->all()); + +// Use the HTTP proxy +$ql->browser('https://500px.com/editors',false,[ + '--proxy' => '192.168.1.42:8080', + '--proxy-type' => 'http' +]) +``` + +- Using the CURL multithreading plug-in, multi-threaded crawling GitHub trending : + +```php +$ql = QueryList::use(CurlMulti::class); +$ql->curlMulti([ + 'https://github.com/trending/php', + 'https://github.com/trending/go', + //.....more urls +]) + // Called if task is success + ->success(function (QueryList $ql,CurlMulti $curl,$r){ + echo "Current url:{$r['info']['url']} \r\n"; + $data = $ql->find('h3 a')->texts(); + print_r($data->all()); +}) + // Task fail callback +->error(function ($errorInfo,CurlMulti $curl){ + echo "Current url:{$errorInfo['info']['url']} \r\n"; + print_r($errorInfo['error']); +}) +->start([ + // Maximum number of threads + 'maxThread' => 10, + // Number of error retries + 'maxTry' => 3, +]); + +``` + +## Plugins +- [jae-jae/QueryList-PhantomJS](https://github.com/jae-jae/QueryList-PhantomJS):Use PhantomJS to crawl Javascript dynamically rendered page. +- [jae-jae/QueryList-CurlMulti](https://github.com/jae-jae/QueryList-CurlMulti) : Curl multi threading. +- [jae-jae/QueryList-AbsoluteUrl](https://github.com/jae-jae/QueryList-AbsoluteUrl) : Converting relative urls to absolute. +- [jae-jae/QueryList-Rule-Google](https://github.com/jae-jae/QueryList-Rule-Google) : Google searcher. +- [jae-jae/QueryList-Rule-Baidu](https://github.com/jae-jae/QueryList-Rule-Baidu) : Baidu searcher. + + +View more QueryList plugins and QueryList-based products: [QueryList Community](https://github.com/jae-jae/QueryList-Community) + +## Contributing +Welcome to contribute code for the QueryList。About Contributing Plugins can be viewed:[QueryList Plugin Contributing Guide](https://github.com/jae-jae/QueryList-Community/blob/master/CONTRIBUTING.md) + +## Author +Jaeger + +If this library is useful for you, say thanks [buying me a beer :beer:](https://www.paypal.me/jaepay)! + +## Lisence +QueryList is licensed under the license of MIT. See the LICENSE for more details. diff --git a/vendor/jaeger/querylist/composer.json b/vendor/jaeger/querylist/composer.json new file mode 100644 index 0000000..e1bcefe --- /dev/null +++ b/vendor/jaeger/querylist/composer.json @@ -0,0 +1,40 @@ +{ + "name": "jaeger/querylist", + "description": "Simple, elegant, extensible PHP Web Scraper (crawler/spider),Use the css3 dom selector,Based on phpQuery! 简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。", + "keywords":["QueryList","phpQuery","spider"], + "homepage": "http://querylist.cc", + "require": { + "PHP":">=7.1", + "jaeger/phpquery-single": "^1", + "jaeger/g-http": "^1.1", + "ext-dom": "*", + "tightenco/collect": ">5.0" + }, + "suggest":{ + + }, + "license": "MIT", + "authors": [ + { + "name": "Jaeger", + "email": "JaegerCode@gmail.com" + } + ], + "autoload":{ + "psr-4":{ + "QL\\":"src" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "require-dev": { + "symfony/var-dumper": "^3.3", + "phpunit/phpunit": "^8.5" + }, + "scripts": { + "test": "./vendor/bin/phpunit" + } +} diff --git a/vendor/jaeger/querylist/logo.png b/vendor/jaeger/querylist/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a29b3e667a0ae12d30e0deda5f4a73d5610ab1a8 GIT binary patch literal 15609 zcmeHuXH=6-xNQ&wv7sWpDhdM9I~WiV5NT3F3qb*C(g`J01sheV(p%_|h!Bxb6$KIL zB_Ca+1tK7jfCK|~_|9GHo_p51Kkxl>&thc-Ugn*7XWp4-_Oth%m-mcx&z$5w34uV) z=-s((3W3m(k3J_D!Ihq|*;w$!;G$`$34zomo%(zKIQU!8@s6n>1cJH>fkZ_^AbW@4 zdj$ePN<$#44iJdy8wiBMI~nms2b5)eps#xya(MJp&{CEHt}y%DvG#{RB!!MXbipMW zf#4!jfS#c?(;PGZY38#jC}ST`i@V-!P4nQ1wdo)S-o@O*&3?H_v_*z183nKFBKX0&TN^4MFW=JP-hR2Q$5|z3X3qJIv89uoYlVDdAPLU3(L zmPw9}mpQ4TsZD>W6LTlCN}A@MJnG;=*SDpA?E8& zrLiT7LBVCz`;$ZOQdtwlE}Z7;t$A(oRr#cHqQR~Pa#3tfcr?@CTe$6jL?J*PCE`vMQ_Zmud8> znoYpl5+h2jr6<%B15X&87`Cd(O|YnR^R{%XuGx=%alnoG(ej~tKIih*E1795yd4q0 z-tsXi(wR|7NmS1-zWA4qIqSPq3Q(-o_Oryn`l$yA{gNZE>J6Nq4K?T0>rVWX84Z;8 zKsYA6A4wx6WYH$fVW1(4N|LL^Wv&fs0t5f_C!>31r@1OzrGHhG^607YRGJUkh}c*C z=6SMy3a&e+qmwxoVE9dR$W5!u{r+6AVK*P~EQiu$BI@#jCacDSkDFY^2^?>dmcZ; zHosgYI)+)JJgZ?KOk8*1p#RplXOYGu87dKI=#++Qv&xdgZJ~`|c6Nk&nBu3Xe-CORp z8n#JIqHzkL7PB)`$ef4WV#gt5g(vFwrqIWP3uMN%kb_?{@lNE5ESR#T%3L`iWzWb) zx|(b6YSwE+QFduwNw4lV@?%TB+@ELQ^E3_gUyAGl)=qY z0iYu8G;4=7KNKR4u|u`Sr}^kf)`l}y#i#r zWv%5jgJB$N-$3%uZr+65C$CL>b@DLT0i}`xY6z@F#_DpDp4SXqf5f@Tc_Ey`Dd&@d9{>2Q58e}+nL5H}# ze$>jqS)?#)4uZ)6`XoYj3LQJ-AFyBVAFw^*FMmC2o^m@l`;H(wPN#S1Q_&{Ml^{lQ z=<%PJw)!%t3aL6zW}U;IR88y?&n&^_Y-#4oJbJ==dCoTE_33Gu`5U=v53*XDHj_{f z`fYHXYA4hn;^)bEeko#oUx$}naGE;Z}p-unEFf~H>#7m+wObjFO`BVx`fJe-E5Owzosbv7a)>~qqK z!BgmWT5i;>UvZSLL$(2+qYv_aK3vEky0}c2M`j+LXDf9P2sf92pj8tGlD-(aKj@RD zk(!f;$+iA$KTTeaeLY!5!dP>xq+TkfEE4htNVt9LCrq!lf;mE264BfG4~d)34Xd%6 zr(QHZ<#i0O?+&1CO-&+uKg(V8JvMFpi9TxN_P}f(l1(wuE35f-oAUC*TpQ_5(8d1F z<%=tMa@b**G41WU4!7HUH@`jTrb%F`a(wUy_jkVF9Xms!cg7c@O?0jmN#*<1>NqW| z_IN~8fY6!#gAUF6L{iVo^y$htwc$97IPo&>qEE1#z@sNdHwi|nX5(`K(>SWkng{06 zD#Y~UhLxA;RjxhiW|0SyhL(G|C%D8}oD`p1#?;@cC%3Kt;TZqTyIfNgzm%+3aP)rq zR6$Q??|i!ao#gb3#)I@Ov*R*q5nQ0i-|@?30e^=bB2}%w`2uywh4q{&44PRKu^+;89pv`v(lo`nOOQtk%I4rDU%3 zELc%t%r{rxZkWQADl+%3Qip!Uv7LF=lGmwZN3hV9#oU&ySn3NLMb?)~j#XO8`c6ek zx$-VP8%`7GHn&tZ)D$Ur%lfLLSo^BpC6NND@A!M(Bm4sb-xN2$T0rt@uM;t{UDH zLKhlzj{S~;Z?t0qdSix>EB`dw8_O14=tfsgaM#xyyt1zm8!Z)L!mZrNbXUy+(Xr^Z z>|o3J*g?bwP8NFBSl^`yJYC5%t?9B@csD24{526?3r(BShf4_q@qf>&w)i|LT}~MI z9dGwECyFqeMdxob7#tqaxknw!LWuQg`$29v7!ORNA1d!u^5F06puZ{@G}S5&V3GQ!R#W2*TkIXd z>ercjs>rX`j-p@s>HKQnaV@sMUx%4f(jm2Z5|L(+PAhy-Ow}P7hNqrUzKnC&M8?^K z>sDF9*2aeS$4F0O*CuA|s)8@iu-VK9JP#90ifU7yPh_Xx|!?fY5mXHf6)_1!l4;|Th zLvOKHmz+)GAzQ6*dzO!5$NCc6_@An&t8xFz6Bn&$E-^ArZ7rSsHCw>-)&{vGCv#RyKq;` zvwExnahS_54lgE&NDPg#8WH~aRcLQaa1mZSJDe-mH=0`|U?IZsm5x(T)Q$-R(z-33 zRj`ZO`loW}f2NRUDEI{_jS&}1DB4i>>^0Gnz^{ktGJXjrZjQLt_v>!(zh#GG;gbkc zeV6Bi6iQ~2p;dX^GvkN0RuFpO67ll^Pw<`-)e2vHXq<~4ABiZod z)`wu(!r5Pw1^W54*Xz?e$yqt%8 z`6~#-VGZ{K&uCKlU#s}yvD*e3Q(HNh@J;oW5cCx3CCa(`cQ?M=NoUt695N7T`c81} z<{c#?`|7DXBP0`}iEQ0($obfP%AY)i*e1#S$TlrPBgKWqYj9NRZ+?1)Hw9IY!pv)}%giA@}Rm#N)`+QN0ib#22;fw5kqaxZ^_0Q@zIG&bJ`J_Oh%HmO58!$DqffVvGkr8$h6D>e{r0m`P{KANDR-G7DDtaUXz>X0 ze{ODwFaGwPG5kKI*e1mFIe5^w0E)W0ulpnp(UIduD&k11)pIHG9$)W((YfT@i=+Ok ze2!~F5ZXULW`7C7N!<+hsn zi@PlOCki(_sVS-{%vjypbn%ruP|M~JtcAa%>)odn>HyK~pF1aR=Ig&KaCtU_94sHV zPa1Sgt2slRz?WGJ+H~;*waL0C1QBg{BB~N_OwIhIU41+<@Qxw>e_R=F=5IwF1^49) zSEG-vSdo?2!Lk*HGk;xL%o|fmb{;$Mu6b1tDG@Y=aRL!1-=wM;lyM}A5Fu3alNY;) z&F6>HIJ$qMUh(hseC)*s?d$u6)bT5K4tldR7mDmuZ=#aaM)j?hvk2{sso5b$$b2 zBg0<4(v`idV1)^>Y}nLfR0YdPy1|A_H_`wSwqKAP{x^$ z#2_ZcRm#PG9z8S}WH=snz|HiEJB)|ydadZG_WV)5pe;cyfeMzs0qY{2<&`y>N$WOi zlq(w^ORp}RdFgbt)(L;P0F)%ghZ%n7TB2O(LZVx&jo08^*|kz!G|Z!v9Yww!)T?>! zYz1>x|C3*y`a)1W4VnTO@bW@8YkOlqyQ<1SkNMZpQ$g#xo#pkLZ}XskN(m{vu!q-* ztV4ua`01mL|Fym>G@GvxbQM~6cm{`j9|}F0cxgqL{QZD1%AOPg{pQOF5M8X9A_Uc& zp~<35XZT$djQ)J3yXSz%Xi4+={Y)8D4GQ~y)FK$#?Q7UCZW(wDe!YpFc;@gp82u~B z71rvr>qy#M%fz777r!Pf*J!dSKlzav0(TwNpb;L)BX z@()Us18xU}ZI({3=GwFP860=Z%zE{2C%YmwgW$biCB^{70Rlv+&|0sJ1+IrAGa#hH z{5Vw$6WsSir@&j zbHDWzs@>O|IKRET>2wQgzV^Zh=rJ(`32rrDgdhT=h&AA>2j{;AD_1HQm*W7 z>}cU%c(dwQGckIv`(wym88Hu)qS$m*FdC(=xk9+l_bx_Xj!Id=7BrcH^eIdLajv0k z^X-n}sNca2Q2WFVyXOtlquG$kX0%Ho@kbr*i4*~$-Yo7RL4{mNV}nEdg)>VMRW>2&4**ph-ucC5<$0Rj^{=R zFQ0Z?WNO#jh7DY0D_Op_Fl;+!M;HZ*%y-W8q^yn_NqqF;_QuTqm(0BfpkVhFe4tp} z-U~}|9UQ}-Oe``rB|P;7jMh=F$TwdQ*#B*rN4HpZt0nK}o2;LFs#Am4LAh72v((hg z33IcygxKQ7SmlH8PlAxhZHs|uqnpLD0NGJ@*l_`Y1KX)i<^mq0>pF*Cbhf;`CJ(P& zHd=uwbAeZom*hP?J{e$@@atU0yPw@Lm6O@c{PS=8E_1~)3Xyr+^)@XA60UT0?OVfe zN)>XMzfq^jw>$v&5#mj8j6c0RK$mj`F)Fvfkh(PQ|KjtFFO7O>3j2S?6y~q}99Fvl z`I8?BlD1gJj&ICmq0nxb(Rzhg%D{-_pbhr6>zK*QRq2K zooheATnQ-0JB9UJy{nMzi5JL&)+Q+NoEE)PB4@a zB|3+1r5UgX9{YAmcW>at5A>gFL{pS>wNHEg@_-s(3Ln(`Jh^)@kgU7h@4y&eTt62$o1O;bda|YO(sbD zF$O*eMj?_l#pU+RBRGQg#EOIv4cy(J& z>fS?X*@ER!`p&PM$!FWu518?#@PPdD=KVsKNkLo5CKC9RtB`e{ff%eAn& zYZ?;UF^7~_tRu*j{*hvR?@_m>^GsJ(!C*zrS7p#lFeI^bA{liWBKi%s@o4R(5mNZ4 zebQ#$vCeC1oSUfAhd%H{ZCP^e@eB{6k8e^A9OL8rc-8e-a{cQhdDO6x{Z&oLo14j@ zL&(vI@i~AvO;eMec#TYrV*>LTp{~cK8y@)Hy}7D7MEhBNiZUltw$q)jbgAslu4JQT zqz37-^YLNEMT_6QN`=%u#g}P<5Rs3%ni)1fWpJNqAu$i)P41)K2iK*=+rc&5Ua#l) zwjL|pu)nqU`gFMefi5{R4mD`Jq&Af3y?nPw+(oU=#{zDPofAGtr6;4xvY0nhM((y z7yW`vV@+H|EW=Cpp3XBaXjrG_6(8FDF$wpcvw)YWp;{QcQ!~Nhi4!^ zTv{v5J{eYC9RnD@OJ2CD0Fj<)rumhbcnZ+TC63EyiIGZB&N0v7^wonp^$O9(FDe06HOxr(|)kq*Ld-j7KTqZD2U1K}!c<$rb z)GsnR0-OrANfJ#K90@t2v?AzW{^P3Zds&?>9C<(5lzafWGcq#XhvbGB#bTdGDpk;W z73__D4F(;oxT4o?;Il`-oTJq237A+aPY;_sWZ^1d>TA_&Ft)8Ew=?I#f=U3t`pkI& zMOEPK|Cp|d>l(LWZ13K*9)OST{Slk}d#6;`nd3~JPyoRK%!XEl4I%F36ro8#*wMN^ zRgq^;X_Vd{WL3egiz)H0O{mS3ajU7N^2Y4W(i3?OPU6v}R@uqwXC5k_8zEL7JCoS! zY*)h6@h^equWPW4dJ}Z#R1CQS=mmv!-eaFWG0Q?jT0US(7uoYi*m3kFt;O2W^SI*$ z8h^xz0BzRV^yVJ4NPD5|YZpF-EIfYb6ws4e7tm8*7v5-5=@_4$WKD~+UuLt0{aiiB&h;A)BPR`z$!vhNe9VSBj#DY1s081?2>EP&ONp0bv!_`LqQyNg_&A9 zC}=M;GpFtmF{|6xsIC9ZFk(3b2@|{9ztj6Pe50fYXT)Ti_ySCJ>!Z<6yj}J!EKCYz zBZz+bbiO8RHi6;R5heN`iK5?-kvKP2H)FbzM_9`8PtEqKmucBT(vhG^nCBaw+Ywgy zJn?~v4`4R!l@uVHcT|muL>WEHz~^=p;tqUrS;RSdU_hV zaSm(`oR-cVDWG80x=&;S-c@C}pN<@R_{1yz=_apUYzhlkpAisDTPxQk)4acD%|XeYPt9QKTLpbKc}MhnOu7-hTOd13mF-BqLnsOs?ld*g^pzy1749 z2bPeJZN5{=s6QfZ%R}B9Oxaew~QHbevzG+KJ|s$5&wU>e}~ z%M-w*&}oCiyElMt0-_r|St?<1;GmLm5ZGw~*R47T0czYhjNj7QzXm*UJfU*8{9VoC z?PUlxCMkNxd9RSq-T1M1W&gFT0`bm1o-o~&WT$x5WajBM#}bTv1a|CQ{NL7OOcmSn zz-yU54*VhE9m|?Z6IQmR)1q$+^#DIC2Z-Dc)t^3cA@yW+#97t`j=HHEh_%e=HBGE| zhqJG$(zo_x8gTi`7vN|{Nt5W6&;;ffmCR2=^#-2g0?Ty|U=uGG*W%n1j zBkD8PD|}CLZF7%N)BcayI6Lyn2l$zrvzy@q&hhp&GbtB(){{XVI|5q{y8~E}=%5vN za=ghc%n@q~Qx9seSFax?y$qYbr{7|)T0c#afmjJ&eG*Y#quHPCeMBp%IsS8n)*&iz z@5;F?)WwY!&z^ts*Q_!xa47FIz$ydXV2%nsYk;P5Z!Z3zM|*h4(;8{jV;he)HdN6k z7+5KP$e5XZz`N#+87)JE+(B4xqt7-dprEf^0F=v{X|k_X7I*|9*NmLN%S_y>L-4ro zgo1H8+PR*asTVXVYRJ)Ua~8k9Z7@5#RsFwM-5a&VK$5AP>TUsV)y|!vB{U_oY`10? z^@>#%;I6~Q-^JXyZu>%(p_Lvz|7fIn4q5acTJ`@5g?DT2gj@T%KL2J*Vpatt;APE> zE5#b=o@1@09e}5})hCu$6p8R@-1=lvbih7O?8W;{fA|>@9=VMe8B$SxAMd&Th%mgb z`1S#P0Rn9_kT3{*2DF2AVZcM&&qx9?UN22eJNo|7&E=&n)`xF*GVuPRLaHMmBHyNY zAxFJhY+9bj%|Xa=_ z=|W6^6ra74tOvEn>lo%%_}&+Z8c8wL%ppMFvv%J;&Hb z!sG}*f~dB0YZjyN80L$2EZk2)Sb9G(O&aGMk*@ zDJ7bI+4#O%dI(T?=Smuga>M-=AgTEwxJXP_m;*@3>Qaflu?gtzQd37AaXOPU!D~ zO~z_+sii>Ry{Q5Biw1aea<7h2TF~_1UOAM1nC1PAvXU=m78iRrQ90`8{hp{sE}WLQ z&W$bG#SoyA_9N7+@KuA)SG%s2D2e!{oaEFU-qu~|>~>kL+$^)+_E)n`^d$#50t6RW z*!pL69`!yf4*xFilZFd~Uh-+iR1;Tm?3gORfK8IJ?_9fO!3<4(VuE-2UXhh$TODI( zfm5gy;#Z^M$`~3*ClBK9-3IS%Xtuxg-fT`#1u&Ep8pL59Y}5c44jeFAn@*|85%=Ssc%fP4>u!h#gKbQ&wOe72tzhxSH zQ$^5(=S_(d37fM#ZKS&x@h)Ha%W{b2q&MK#Uc#p$=5J>KOO@*l{2?T@Q<8+#sX6M{ zGnat3izntF145Pz@J`}AkCp>kf(CJUIqchn1DdJB=0?~%_A}Y&CY#kGFI7aOL$M_+ zt+E+sRC{~bcJ|d{mgarJJ`r$o9kN^USciDYYng>$&=n+`?rA=krrf@AZm8F+e+->- zpZc>Z$t}D$;xOES=lfI;jpYaQ^xs))HQ@Q^90cP03I{MF0d*8W?KZ#o!LQ%L!oxn@ zg3EZdih;%teyi71&eQ;~e*<6f)8np=D10yf$`9#|t)5Q2J z@YndTULIAQ`FGFU4R-E1GYI*;Fv_poa2-8-bWnHT(o+X^;E6Vd4LnG?N4Hoa^iuqv zheCUscps@3b$BLN!m-h<^Iu&f{S&-w_6k7y$VArq0Do@W>FPavpVk9j>fyMO*b$Li zM0c>2Q1tOd=>nU_UHe)|UKM?e9iUCi)yFD9Zq6ow6!XBCnKc)a2oKiW*B_agxjdg} z+U-nIrgH|o=IIhqp&U$<@5M7Q=E<+i&U1x)J5eGkl2clJPu1Ix*G2#z!i~9c5T5=e9c3yyRqXM`t?2YNk6(-ilshgSk14>JF zML_!aCCPL%xdv>@^TZM@HF7^Gq9ncVEVhh}dgOYGRHj5z_f(YJQ1;9wOb)&SSGzf( zcGRqhw~ON#+XzUUawic)B=YBXljOOo+8(x2-BXBOcgklKo zfz82*>BzAJ>Qwx8Dp$OZJs&5!ngt(k5?*bHu;1>%h)2_%I-}P!62xv-codk~bo~L1 z#&~HzT9U6tgLWjKmrl?hbs^42u$-s#`dI(Jt|r|JNQYNkyhlB79eynf6!;E6>;FS? zi;B4KRA23c&$9~5mgJLz!EJ5dGO(Oxj20o9?xt<1A(tz40qZ<-R8sCL)@oOxQS*rO zv(AArF+K&(B!D*ie?@9V9CH3bhd7fUlD}ph)mIn#N-9;Vu0&MCBy|rW^^P| zwK9M`3`oL`cz=oz{^6c+ebIv^yHkCdEOAT3HlxFGNddWx#h~_E(W*pK*hpm4;Tibh z&+rMW4hhfb>4Q@4>7A{9%D}qC-Mcns@X92qLk-%auDf3@_+qZ|2JEV&T6SV~6fN~{bn(AEs~H*RTfg{K6TUkXj(W6P6)VDOZyZDS^De65K@%o& zrOd{paelg1P6y~QwEpxq;#e9BAVwxguZ^wzU7C*E7(i9(^&g?r-&ERPi!Y`b;_#Hk zd{Pt7VZoTPv0EM7MYa&}D@+?E)dmB|mQN&5hgr{f9bT5C5t(FP{ZG2=@@NXCD)hZ1 zL8u9__?7o9r{UK!KDFQ@yJAz7u?LehENNEpKr^>mmCjO#xbcM(d4(14y?+wEH z&;b(JkOHtdw6z?B{wsN0Kvk5~!FQV*?J}VIZB*K0r4i(J)P+Jh9f_A`X~f=T%FBjy zQh4b*oF*b-joGsb`=eP!`o!;szcfx=g|%KPm5Lx`E$+I5q_FYV`?Tcu1c9MmFrk(a z;pR-C_|Fk`Iv5!n``Uj~mAq^4`RueD5D;gr4n3ejt~@eKj4uK1K{GgB%&5}f{ioaz z=ZyF7452QhUIi|_U?>-=K!Sf53v%tY)`O-t{+gY|tvX-x39AxY`-po$8YX9QEsJrV z4&9;P{g8)=X}~fop4SU;_^~+%(hglf8j_Pd@<(*@n{1^7Cg!q*WSg@|c)$+a%(vN; z1CEQ22XK^PBJf>Na1>*be-;0crJ}!eo#R`>iY-r`NobMTBSHGp{Je$v*Bmbf!J)RT z+S!SzD|o%QfXRA)d0`LWIG*#YwBZQ=&ErES<3OliQ5V}-?#E7Zm>oFn>jR~+WJ99) zuK)8yt^sUQb)zN1b~k{a_1wyxiIMG~GynQL(4S2XYd4dq1Np63Fq@25;GZ82Ui28& z0=sob?iw<pZgU z9si!+Y9O^P{Tv*EQ67drZEgB-GFQXzVZMFk`}ZhpD|v!-I=k!7FA|fq!wFO*QT+h| zj3p#_6P`GAn_%kngbGM%Ao~`#rh<5mQ3IJ zik&F0;FU6J=al{t5(K7}mIKElCMFHnHnQF+b{NS5A&Wnm$&WAoKyo)*qw~Z_3{M01 zHL!aG02hpEi|dnTXlE4k{#{p)b5c9@$vsdHN>_9?4qKt4rBNzVj?nt`-}ba0z01heS0<`$PWV24b!{`h=PVQA3)w&> z2Fvc~MN0u+ooBDQMT0^Bv{XJ#=s;3OPp?hmtqD_B67dN0Y74n^t2~Yy&y$!e@@~b2 zoaXpQcTRKHXxC)_b>c%P;-EB7YPPcZ&a={GHWK+0v#Q6RD?qv$JhE2usYMz9H6IvW zCTw7ZX=!S>p94mo7`5`OoziuegzRF0C)G~8p6aHhJobMp4rX&$R!T+nQT3*ERfVPi zbD}BPT~<`HF2Nt89t3a@DtPFH_lM+3%KQt4Pk?6}27{JsBCiRY%kmOfE3M&oY3J|u zF7AP#^n5bAzN+wUc2cSv(xL*K)bK!<)lB?cn@(!3RsPZS6Cca)1n)I;Ioxr*1e~Q% zS@J$25^6xS>u7@6^7_M63d_}%wr_x$E4H%5{&ZxHv$*oT4sCZyqw1h$IEFr^U_Wz` zFIcGLxDa<%dI|8nC|nKtP`b?S;XZQ$+;zmi0ut`&F--*TXn^oM5Q!|tCkkT6P{3SA zN|=yctJXlTok;^HW6s|cm!#F2;^29Pi_9^pz+G#;bZ$|%h#RPkQ{ebY-6;j(&~niHV$86D zyo7#cp**m;I>jH81-ASg{%7(l!feLEX+-sBi6X4vtO+pf)+K(nVblL?F=k!`Xj0V+ z9v0gAxrqVwM2DX9qXG5hLNgV&WoKx|1Y&f5hAf^Up{g4)44e zUAg8u7kc6c`~<2}jXOloTf#<-?kT;dN~ekZIn#(MT3VMgV;P#(LlYUFd^@9^#vgkP z$o)d+Ows$hJbKIo3m2eS1($(Ijzb>d?b4;&2FW06Ir8@k@kzKR23=dh$6i%UGZ1ca zk)EzH_`Hw)ptl%PO#Srz6nQUm892}|$mhGOj{UP8v?WO5%L3aC&)T5FrF;SBneeQx z@TA%8#KUpgA#)%Q8Nrxfc$E-A1{yE~d{-Q5dmt>sc>jI;@c-6%%ma-=wHRp6nWZjp?(=GZ zwpD=B{QzfGM?Yuq1(B7OQIe2Wk&u%%myuPKR#BCa7nfF6m6rDM8qxaSZt(PWa(fu^ zf4)IL?h!aC3o$xs!QcB~K+t_ZXNaEXeHUlL`z~&d9)gOJDv~l1GSY%3GKzx1%8Cd@ zdBOi^URgp~PS7Td|@y^l+6yo?#tr?nwQV + + + ./tests + + + + + + src + + + + \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Config.php b/vendor/jaeger/querylist/src/Config.php new file mode 100644 index 0000000..c3b59ed --- /dev/null +++ b/vendor/jaeger/querylist/src/Config.php @@ -0,0 +1,94 @@ + + * Date: 2017/9/22 + */ + +namespace QL; +use Closure; +use Tightenco\Collect\Support\Collection; + +class Config +{ + protected static $instance = null; + + protected $plugins; + protected $binds; + + /** + * Config constructor. + */ + public function __construct() + { + $this->plugins = new Collection(); + $this->binds = new Collection(); + } + + + /** + * Get the Config instance + * + * @return null|Config + */ + public static function getInstance() + { + self::$instance || self::$instance = new self(); + return self::$instance; + } + + /** + * Global installation plugin + * + * @param $plugins + * @param array ...$opt + * @return $this + */ + public function use($plugins,...$opt) + { + if(is_string($plugins)){ + $this->plugins->push([$plugins,$opt]); + }else{ + $this->plugins = $this->plugins->merge($plugins); + } + return $this; + } + + /** + * Global binding custom method + * + * @param string $name + * @param Closure $provider + * @return $this + */ + public function bind(string $name, Closure $provider) + { + $this->binds[$name] = $provider; + return $this; + } + + public function bootstrap(QueryList $queryList) + { + $this->installPlugins($queryList); + $this->installBind($queryList); + } + + protected function installPlugins(QueryList $queryList) + { + $this->plugins->each(function($plugin) use($queryList){ + if(is_string($plugin)){ + $queryList->use($plugin); + }else{ + $queryList->use($plugin[0],...$plugin[1]); + } + }); + } + + protected function installBind(QueryList $queryList) + { + $this->binds->each(function ($provider,$name) use($queryList){ + $queryList->bind($name,$provider); + }); + } + +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Contracts/PluginContract.php b/vendor/jaeger/querylist/src/Contracts/PluginContract.php new file mode 100644 index 0000000..6fe165e --- /dev/null +++ b/vendor/jaeger/querylist/src/Contracts/PluginContract.php @@ -0,0 +1,15 @@ + + * Date: 2017/9/22 + */ + +namespace QL\Contracts; + +use QL\QueryList; + +interface PluginContract +{ + public static function install(QueryList $queryList,...$opt); +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Contracts/ServiceProviderContract.php b/vendor/jaeger/querylist/src/Contracts/ServiceProviderContract.php new file mode 100644 index 0000000..e214263 --- /dev/null +++ b/vendor/jaeger/querylist/src/Contracts/ServiceProviderContract.php @@ -0,0 +1,15 @@ + + * Date: 2017/9/20 + */ + +namespace QL\Contracts; + +use QL\Kernel; + +interface ServiceProviderContract +{ + public function register(Kernel $kernel); +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Dom/Dom.php b/vendor/jaeger/querylist/src/Dom/Dom.php new file mode 100644 index 0000000..c83bea3 --- /dev/null +++ b/vendor/jaeger/querylist/src/Dom/Dom.php @@ -0,0 +1,30 @@ + + * Date: 2017/9/19 + */ + +namespace QL\Dom; + +use phpQueryObject; + +class Dom +{ + + protected $document; + + /** + * Dom constructor. + */ + public function __construct(phpQueryObject $document) + { + $this->document = $document; + } + + public function find($selector) + { + $elements = $this->document->find($selector); + return new Elements($elements); + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Dom/Elements.php b/vendor/jaeger/querylist/src/Dom/Elements.php new file mode 100644 index 0000000..b207b56 --- /dev/null +++ b/vendor/jaeger/querylist/src/Dom/Elements.php @@ -0,0 +1,260 @@ + + * Date: 2017/9/19 + */ + +namespace QL\Dom; + +use phpDocumentor\Reflection\Types\Null_; +use phpQueryObject; +use Tightenco\Collect\Support\Collection; + +/** + * Class Elements + * @package QL\Dom + * + * @method Elements toReference(&$var) + * @method Elements documentFragment($state = null) + * @method Elements toRoot() + * @method Elements getDocumentIDRef(&$documentID) + * @method Elements getDocument() + * @method \DOMDocument getDOMDocument() + * @method Elements getDocumentID() + * @method Elements unloadDocument() + * @method bool isHTML() + * @method bool isXHTML() + * @method bool isXML() + * @method string serialize() + * @method array serializeArray($submit = null) + * @method \DOMElement|\DOMElement[] get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) + * @method string|array getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) + * @method string|array getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) + * @method Elements newInstance($newStack = null) + * @method Elements find($selectors, $context = null, $noHistory = false) + * @method Elements|bool is($selector, $nodes = null) + * @method Elements filterCallback($callback, $_skipHistory = false) + * @method Elements filter($selectors, $_skipHistory = false) + * @method Elements load($url, $data = null, $callback = null) + * @method Elements trigger($type, $data = []) + * @method Elements triggerHandler($type, $data = []) + * @method Elements bind($type, $data, $callback = null) + * @method Elements unbind($type = null, $callback = null) + * @method Elements change($callback = null) + * @method Elements submit($callback = null) + * @method Elements click($callback = null) + * @method Elements wrapAllOld($wrapper) + * @method Elements wrapAll($wrapper) + * @method Elements wrapAllPHP($codeBefore, $codeAfter) + * @method Elements wrap($wrapper) + * @method Elements wrapPHP($codeBefore, $codeAfter) + * @method Elements wrapInner($wrapper) + * @method Elements wrapInnerPHP($codeBefore, $codeAfter) + * @method Elements contents() + * @method Elements contentsUnwrap() + * @method Elements switchWith($markup) + * @method Elements eq($num) + * @method Elements size() + * @method Elements length() + * @method int count() + * @method Elements end($level = 1) + * @method Elements _clone() + * @method Elements replaceWithPHP($code) + * @method Elements replaceWith($content) + * @method Elements replaceAll($selector) + * @method Elements remove($selector = null) + * @method Elements|string markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) + * @method string markupOuter($callback1 = null, $callback2 = null, $callback3 = null) + * @method Elements|string html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) + * @method Elements|string xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) + * @method string htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) + * @method string xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) + * @method Elements php($code) + * @method string markupPHP($code) + * @method string markupOuterPHP() + * @method Elements children($selector) + * @method Elements ancestors($selector) + * @method Elements append($content) + * @method Elements appendPHP($content) + * @method Elements appendTo($seletor) + * @method Elements prepend($content) + * @method Elements prependPHP($content) + * @method Elements prependTo($seletor) + * @method Elements before($content) + * @method Elements beforePHP($content) + * @method Elements insertBefore($seletor) + * @method Elements after($content) + * @method Elements afterPHP($content) + * @method Elements insertAfter($seletor) + * @method Elements insert($target, $type) + * @method int index($subject) + * @method Elements slice($start, $end = null) + * @method Elements reverse() + * @method Elements|string text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) + * @method Elements plugin($class, $file = null) + * @method Elements _next($selector = null) + * @method Elements _prev($selector = null) + * @method Elements prev($selector = null) + * @method Elements prevAll($selector = null) + * @method Elements nextAll($selector = null) + * @method Elements siblings($selector = null) + * @method Elements not($selector = null) + * @method Elements add($selector = null) + * @method Elements parent($selector = null) + * @method Elements parents($selector = null) + * @method Elements stack($nodeTypes = null) + * @method Elements|string attr($attr = null, $value = null) + * @method Elements attrPHP($attr, $code) + * @method Elements removeAttr($attr) + * @method Elements|string val($val = null) + * @method Elements andSelf() + * @method Elements addClass($className) + * @method Elements addClassPHP($className) + * @method bool hasClass($className) + * @method Elements removeClass($className) + * @method Elements toggleClass($className) + * @method Elements _empty() + * @method Elements callback($callback, $param1 = null, $param2 = null, $param3 = null) + * @method string data($key, $value = null) + * @method Elements removeData($key) + * @method void rewind() + * @method Elements current() + * @method int key() + * @method Elements next($cssSelector = null) + * @method bool valid() + * @method bool offsetExists($offset) + * @method Elements offsetGet($offset) + * @method void offsetSet($offset, $value) + * @method string whois($oneNode) + * @method Elements dump() + * @method Elements dumpWhois() + * @method Elements dumpLength() + * @method Elements dumpTree($html, $title) + * @method dumpDie() + */ +class Elements +{ + /** + * @var phpQueryObject + */ + protected $elements; + + /** + * Elements constructor. + * @param $elements + */ + public function __construct(phpQueryObject $elements) + { + $this->elements = $elements; + } + + public function __get($name) + { + return property_exists($this->elements, $name) ? $this->elements->$name : $this->elements->attr($name); + } + + public function __call($name, $arguments) + { + $obj = call_user_func_array([$this->elements, $name], $arguments); + if ($obj instanceof phpQueryObject) { + $obj = new self($obj); + } else if (is_string($obj)) { + $obj = trim($obj); + } + return $obj; + } + + /** + * Iterating elements + * + * @param callable $callback + * + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->elements as $key => $element) { + $break = $callback(new self(pq($element)), $key); + if ($break === false) { + break; + } + } + + return $this; + } + + /** + * Iterating elements + * + * @param $callback + * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection + */ + public function map($callback) + { + $collection = new Collection(); + $this->elements->each(function ($dom) use (& $collection, $callback) { + $collection->push($callback(new self(pq($dom)))); + }); + return $collection; + } + + /** + * Gets the attributes of all the elements + * + * @param string $attr HTML attribute name + * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection + */ + public function attrs($attr) + { + return $this->map(function ($item) use ($attr) { + return $item->attr($attr); + }); + } + + /** + * Gets the text of all the elements + * + * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection + */ + public function texts() + { + return $this->map(function ($item) { + return trim($item->text()); + }); + } + + /** + * Gets the html of all the elements + * + * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection + */ + public function htmls() + { + return $this->map(function ($item) { + return trim($item->html()); + }); + } + + /** + * Gets the htmlOuter of all the elements + * + * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection + */ + public function htmlOuters() + { + return $this->map(function ($item) { + return trim($item->htmlOuter()); + }); + } + + + /** + * @return phpQueryObject + */ + public function getElements(): phpQueryObject + { + return $this->elements; + } + +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Dom/Query.php b/vendor/jaeger/querylist/src/Dom/Query.php new file mode 100644 index 0000000..2899c3c --- /dev/null +++ b/vendor/jaeger/querylist/src/Dom/Query.php @@ -0,0 +1,322 @@ + + * Date: 2017/9/21 + */ + +namespace QL\Dom; + +use Tightenco\Collect\Support\Collection; +use phpQuery; +use phpQueryObject; +use QL\QueryList; +use Closure; + +class Query +{ + protected $html; + /** + * @var \phpQueryObject + */ + protected $document; + protected $rules; + protected $range = null; + protected $ql; + /** + * @var Collection + */ + protected $data; + + + public function __construct(QueryList $ql) + { + $this->ql = $ql; + } + + /** + * @param bool $rel + * @return String + */ + public function getHtml($rel = true) + { + return $rel ? $this->document->htmlOuter() : $this->html; + } + + /** + * @param $html + * @param null $charset + * @return QueryList + */ + public function setHtml($html, $charset = null) + { + $this->html = value($html); + $this->destroyDocument(); + $this->document = phpQuery::newDocumentHTML($this->html, $charset); + return $this->ql; + } + + /** + * Get crawl results + * + * @param Closure|null $callback + * @return Collection|static + */ + public function getData(Closure $callback = null) + { + return $this->handleData($this->data, $callback); + } + + /** + * @param Collection $data + */ + public function setData(Collection $data) + { + $this->data = $data; + } + + + /** + * Searches for all elements that match the specified expression. + * + * @param $selector A string containing a selector expression to match elements against. + * @return Elements + */ + public function find($selector) + { + return (new Dom($this->document))->find($selector); + } + + /** + * Set crawl rule + * + * $rules = [ + * 'rule_name1' => ['selector','HTML attribute | text | html','Tag filter list','callback'], + * 'rule_name2' => ['selector','HTML attribute | text | html','Tag filter list','callback'], + * // ... + * ] + * + * @param array $rules + * @return QueryList + */ + public function rules(array $rules) + { + $this->rules = $rules; + return $this->ql; + } + + + /** + * Set the slice area for crawl list + * + * @param $selector + * @return QueryList + */ + public function range($selector) + { + $this->range = $selector; + return $this->ql; + } + + /** + * Remove HTML head,try to solve the garbled + * + * @return QueryList + */ + public function removeHead() + { + $html = preg_replace('/(|).+<\/head>/is', '', $this->html); + $this->setHtml($html); + return $this->ql; + } + + /** + * Execute the query rule + * + * @param Closure|null $callback + * @return QueryList + */ + public function query(Closure $callback = null) + { + $this->data = $this->getList(); + $this->data = $this->handleData($this->data, $callback); + return $this->ql; + } + + public function handleData(Collection $data, $callback) + { + if (is_callable($callback)) { + if (empty($this->range)) { + $data = new Collection($callback($data->all(), null)); + } else { + $data = $data->map($callback); + } + } + + return $data; + } + + protected function getList() + { + $data = []; + if (empty($this->range)) { + foreach ($this->rules as $key => $reg_value) { + $rule = $this->parseRule($reg_value); + $contentElements = $this->document->find($rule['selector']); + $data[$key] = $this->extractContent($contentElements, $key, $rule); + } + } else { + $rangeElements = $this->document->find($this->range); + $i = 0; + foreach ($rangeElements as $element) { + foreach ($this->rules as $key => $reg_value) { + $rule = $this->parseRule($reg_value); + $contentElements = pq($element)->find($rule['selector']); + $data[$i][$key] = $this->extractContent($contentElements, $key, $rule); + } + $i++; + } + } + + return new Collection($data); + } + + protected function extractContent(phpQueryObject $pqObj, $ruleName, $rule) + { + switch ($rule['attr']) { + case 'text': + $content = $this->allowTags($pqObj->html(), $rule['filter_tags']); + break; + case 'texts': + $content = (new Elements($pqObj))->map(function (Elements $element) use ($rule) { + return $this->allowTags($element->html(), $rule['filter_tags']); + })->all(); + break; + case 'html': + $content = $this->stripTags($pqObj->html(), $rule['filter_tags']); + break; + case 'htmls': + $content = (new Elements($pqObj))->map(function (Elements $element) use ($rule) { + return $this->stripTags($element->html(), $rule['filter_tags']); + })->all(); + break; + case 'htmlOuter': + $content = $this->stripTags($pqObj->htmlOuter(), $rule['filter_tags']); + break; + case 'htmlOuters': + $content = (new Elements($pqObj))->map(function (Elements $element) use ($rule) { + return $this->stripTags($element->htmlOuter(), $rule['filter_tags']); + })->all(); + break; + default: + if(preg_match('/attr\((.+)\)/', $rule['attr'], $arr)) { + $content = $pqObj->attr($arr[1]); + } elseif (preg_match('/attrs\((.+)\)/', $rule['attr'], $arr)) { + $content = (new Elements($pqObj))->attrs($arr[1])->all(); + } else { + $content = $pqObj->attr($rule['attr']); + } + break; + } + + if (is_callable($rule['handle_callback'])) { + $content = call_user_func($rule['handle_callback'], $content, $ruleName); + } + + return $content; + } + + protected function parseRule($rule) + { + $result = []; + $result['selector'] = $rule[0]; + $result['attr'] = $rule[1]; + $result['filter_tags'] = $rule[2] ?? ''; + $result['handle_callback'] = $rule[3] ?? null; + + return $result; + } + + /** + * 去除特定的html标签 + * @param string $html + * @param string $tags_str 多个标签名之间用空格隔开 + * @return string + */ + protected function stripTags($html, $tags_str) + { + $tagsArr = $this->tag($tags_str); + $html = $this->removeTags($html, $tagsArr[1]); + $p = array(); + foreach ($tagsArr[0] as $tag) { + $p[] = "/(<(?:\/" . $tag . "|" . $tag . ")[^>]*>)/i"; + } + $html = preg_replace($p, "", trim($html)); + return $html; + } + + /** + * 保留特定的html标签 + * @param string $html + * @param string $tags_str 多个标签名之间用空格隔开 + * @return string + */ + protected function allowTags($html, $tags_str) + { + $tagsArr = $this->tag($tags_str); + $html = $this->removeTags($html, $tagsArr[1]); + $allow = ''; + foreach ($tagsArr[0] as $tag) { + $allow .= "<$tag> "; + } + return strip_tags(trim($html), $allow); + } + + protected function tag($tags_str) + { + $tagArr = preg_split("/\s+/", $tags_str, -1, PREG_SPLIT_NO_EMPTY); + $tags = array(array(), array()); + foreach ($tagArr as $tag) { + if (preg_match('/-(.+)/', $tag, $arr)) { + array_push($tags[1], $arr[1]); + } else { + array_push($tags[0], $tag); + } + } + return $tags; + } + + /** + * 移除特定的html标签 + * @param string $html + * @param array $tags 标签数组 + * @return string + */ + protected function removeTags($html, $tags) + { + $tag_str = ''; + if (count($tags)) { + foreach ($tags as $tag) { + $tag_str .= $tag_str ? ',' . $tag : $tag; + } +// phpQuery::$defaultCharset = $this->inputEncoding?$this->inputEncoding:$this->htmlEncoding; + $doc = phpQuery::newDocumentHTML($html); + pq($doc)->find($tag_str)->remove(); + $html = pq($doc)->htmlOuter(); + $doc->unloadDocument(); + } + return $html; + } + + protected function destroyDocument() + { + if ($this->document instanceof phpQueryObject) { + $this->document->unloadDocument(); + } + } + + public function __destruct() + { + $this->destroyDocument(); + } +} diff --git a/vendor/jaeger/querylist/src/Exceptions/ServiceNotFoundException.php b/vendor/jaeger/querylist/src/Exceptions/ServiceNotFoundException.php new file mode 100644 index 0000000..a39b428 --- /dev/null +++ b/vendor/jaeger/querylist/src/Exceptions/ServiceNotFoundException.php @@ -0,0 +1,15 @@ + + * Date: 2017/9/21 + */ + +namespace QL\Exceptions; + +use Exception; + +class ServiceNotFoundException extends Exception +{ + +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Kernel.php b/vendor/jaeger/querylist/src/Kernel.php new file mode 100644 index 0000000..d8a3a8c --- /dev/null +++ b/vendor/jaeger/querylist/src/Kernel.php @@ -0,0 +1,74 @@ + + * Date: 2017/9/21 + */ + +namespace QL; + +use QL\Contracts\ServiceProviderContract; +use QL\Exceptions\ServiceNotFoundException; +use QL\Providers\EncodeServiceProvider; +use Closure; +use QL\Providers\HttpServiceProvider; +use QL\Providers\PluginServiceProvider; +use QL\Providers\SystemServiceProvider; +use Tightenco\Collect\Support\Collection; + +class Kernel +{ + protected $providers = [ + SystemServiceProvider::class, + HttpServiceProvider::class, + EncodeServiceProvider::class, + PluginServiceProvider::class + ]; + + protected $binds; + protected $ql; + + /** + * Kernel constructor. + * @param $ql + */ + public function __construct(QueryList $ql) + { + $this->ql = $ql; + $this->binds = new Collection(); + } + + public function bootstrap() + { + //注册服务提供者 + $this->registerProviders(); + return $this; + } + + public function registerProviders() + { + foreach ($this->providers as $provider) { + $this->register(new $provider()); + } + } + + public function bind(string $name,Closure $provider) + { + $this->binds[$name] = $provider; + } + + public function getService(string $name) + { + if(!$this->binds->offsetExists($name)){ + throw new ServiceNotFoundException("Service: {$name} not found!"); + } + return $this->binds[$name]; + } + + private function register(ServiceProviderContract $instance) + { + $instance->register($this); + } + + +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Providers/EncodeServiceProvider.php b/vendor/jaeger/querylist/src/Providers/EncodeServiceProvider.php new file mode 100644 index 0000000..19e0da4 --- /dev/null +++ b/vendor/jaeger/querylist/src/Providers/EncodeServiceProvider.php @@ -0,0 +1,22 @@ + + * Date: 2017/9/20 + */ + +namespace QL\Providers; + +use QL\Contracts\ServiceProviderContract; +use QL\Kernel; +use QL\Services\EncodeService; + +class EncodeServiceProvider implements ServiceProviderContract +{ + public function register(Kernel $kernel) + { + $kernel->bind('encoding',function (string $outputEncoding,string $inputEncoding = null){ + return EncodeService::convert($this,$outputEncoding,$inputEncoding); + }); + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Providers/HttpServiceProvider.php b/vendor/jaeger/querylist/src/Providers/HttpServiceProvider.php new file mode 100644 index 0000000..c85a658 --- /dev/null +++ b/vendor/jaeger/querylist/src/Providers/HttpServiceProvider.php @@ -0,0 +1,40 @@ + + * Date: 2017/9/22 + */ + +namespace QL\Providers; + + +use QL\Contracts\ServiceProviderContract; +use QL\Kernel; +use QL\Services\HttpService; +use QL\Services\MultiRequestService; + +class HttpServiceProvider implements ServiceProviderContract +{ + public function register(Kernel $kernel) + { + $kernel->bind('get',function (...$args){ + return HttpService::get($this,...$args); + }); + + $kernel->bind('post',function (...$args){ + return HttpService::post($this,...$args); + }); + + $kernel->bind('postJson',function (...$args){ + return HttpService::postJson($this,...$args); + }); + + $kernel->bind('multiGet',function (...$args){ + return new MultiRequestService($this,'get',...$args); + }); + + $kernel->bind('multiPost',function (...$args){ + return new MultiRequestService($this,'post',...$args); + }); + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Providers/PluginServiceProvider.php b/vendor/jaeger/querylist/src/Providers/PluginServiceProvider.php new file mode 100644 index 0000000..f20cbcf --- /dev/null +++ b/vendor/jaeger/querylist/src/Providers/PluginServiceProvider.php @@ -0,0 +1,23 @@ + + * Date: 2017/9/22 + */ + +namespace QL\Providers; + +use QL\Contracts\ServiceProviderContract; +use QL\Kernel; +use QL\Services\PluginService; + +class PluginServiceProvider implements ServiceProviderContract +{ + public function register(Kernel $kernel) + { + $kernel->bind('use',function ($plugins,...$opt){ + return PluginService::install($this,$plugins,...$opt); + }); + } + +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Providers/SystemServiceProvider.php b/vendor/jaeger/querylist/src/Providers/SystemServiceProvider.php new file mode 100644 index 0000000..e0299c4 --- /dev/null +++ b/vendor/jaeger/querylist/src/Providers/SystemServiceProvider.php @@ -0,0 +1,32 @@ + + * Date: 2017/9/22 + */ + +namespace QL\Providers; + +use QL\Contracts\ServiceProviderContract; +use QL\Kernel; +use Closure; + +class SystemServiceProvider implements ServiceProviderContract +{ + public function register(Kernel $kernel) + { + $kernel->bind('html',function (...$args){ + $this->setHtml(...$args); + return $this; + }); + + $kernel->bind('queryData',function (Closure $callback = null){ + return $this->query()->getData($callback)->all(); + }); + + $kernel->bind('pipe',function (Closure $callback = null){ + return $callback($this); + }); + + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/QueryList.php b/vendor/jaeger/querylist/src/QueryList.php new file mode 100644 index 0000000..e54d99c --- /dev/null +++ b/vendor/jaeger/querylist/src/QueryList.php @@ -0,0 +1,133 @@ +query = new Query($this); + $this->kernel = (new Kernel($this))->bootstrap(); + Config::getInstance()->bootstrap($this); + } + + public function __call($name, $arguments) + { + if(method_exists($this->query,$name)){ + $result = $this->query->$name(...$arguments); + }else{ + $result = $this->kernel->getService($name)->call($this,...$arguments); + } + return $result; + } + + public static function __callStatic($name, $arguments) + { + $instance = new self(); + return $instance->$name(...$arguments); + } + + public function __destruct() + { + $this->destruct(); + } + + /** + * Get the QueryList single instance + * + * @return QueryList + */ + public static function getInstance() + { + self::$instance || self::$instance = new self(); + return self::$instance; + } + + /** + * Get the Config instance + * @return null|Config + */ + public static function config() + { + return Config::getInstance(); + } + + /** + * Destruction of resources + */ + public function destruct() + { + unset($this->query); + unset($this->kernel); + } + + /** + * Destroy all documents + */ + public static function destructDocuments() + { + phpQuery::$documents = []; + } + + /** + * Bind a custom method to the QueryList object + * + * @param string $name Invoking the name + * @param Closure $provide Called method + * @return $this + */ + public function bind(string $name,Closure $provide) + { + $this->kernel->bind($name,$provide); + return $this; + } + +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Services/EncodeService.php b/vendor/jaeger/querylist/src/Services/EncodeService.php new file mode 100644 index 0000000..6f03255 --- /dev/null +++ b/vendor/jaeger/querylist/src/Services/EncodeService.php @@ -0,0 +1,37 @@ + + * Date: 2017/9/20 + * 编码转换服务 + */ + +namespace QL\Services; + +use QL\QueryList; + +class EncodeService +{ + public static function convert(QueryList $ql,string $outputEncoding,string $inputEncoding = null) + { + $html = $ql->getHtml(); + $inputEncoding || $inputEncoding = self::detect($html); + $html = iconv($inputEncoding,$outputEncoding.'//IGNORE',$html); + $ql->setHtml($html); + return $ql; + } + + /** + * Attempts to detect the encoding + * @param $string + * @return bool|false|mixed|string + */ + public static function detect($string) + { + $charset=mb_detect_encoding($string, array('ASCII', 'GB2312', 'GBK', 'UTF-8'),true); + if(strtolower($charset)=='cp936') + $charset='GBK'; + return $charset; + } + +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Services/HttpService.php b/vendor/jaeger/querylist/src/Services/HttpService.php new file mode 100644 index 0000000..77993c2 --- /dev/null +++ b/vendor/jaeger/querylist/src/Services/HttpService.php @@ -0,0 +1,59 @@ + + * Date: 2017/9/22 + */ + +namespace QL\Services; + +use GuzzleHttp\Cookie\CookieJar; +use Jaeger\GHttp; +use QL\QueryList; + +class HttpService +{ + protected static $cookieJar = null; + + public static function getCookieJar() + { + if(self::$cookieJar == null) + { + self::$cookieJar = new CookieJar(); + } + return self::$cookieJar; + } + + public static function get(QueryList $ql,$url,$args = null,$otherArgs = []) + { + $otherArgs = array_merge([ + 'cookies' => self::getCookieJar(), + 'verify' => false + ],$otherArgs); + $html = GHttp::get($url,$args,$otherArgs); + $ql->setHtml($html); + return $ql; + } + + public static function post(QueryList $ql,$url,$args = null,$otherArgs = []) + { + $otherArgs = array_merge([ + 'cookies' => self::getCookieJar(), + 'verify' => false + ],$otherArgs); + $html = GHttp::post($url,$args,$otherArgs); + $ql->setHtml($html); + return $ql; + } + + public static function postJson(QueryList $ql,$url,$args = null,$otherArgs = []) + { + $otherArgs = array_merge([ + 'cookies' => self::getCookieJar(), + 'verify' => false + ],$otherArgs); + $html = GHttp::postJson($url,$args,$otherArgs); + $ql->setHtml($html); + return $ql; + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Services/MultiRequestService.php b/vendor/jaeger/querylist/src/Services/MultiRequestService.php new file mode 100644 index 0000000..803ee80 --- /dev/null +++ b/vendor/jaeger/querylist/src/Services/MultiRequestService.php @@ -0,0 +1,66 @@ + + * Date: 18/12/10 + * Time: 下午7:05 + */ + +namespace QL\Services; + + +use Jaeger\GHttp; +use Closure; +use GuzzleHttp\Psr7\Response; +use QL\QueryList; +use GuzzleHttp\Exception\RequestException; + +/** + * Class MultiRequestService + * @package QL\Services + * + * @method MultiRequestService withHeaders($headers) + * @method MultiRequestService withOptions($options) + * @method MultiRequestService concurrency($concurrency) + */ +class MultiRequestService +{ + protected $ql; + protected $multiRequest; + protected $method; + + public function __construct(QueryList $ql,$method,$urls) + { + $this->ql = $ql; + $this->method = $method; + $this->multiRequest = GHttp::multiRequest($urls); + } + + public function __call($name, $arguments) + { + $this->multiRequest = $this->multiRequest->$name(...$arguments); + return $this; + } + + public function success(Closure $success) + { + $this->multiRequest = $this->multiRequest->success(function(Response $response, $index) use($success){ + $this->ql->setHtml((String)$response->getBody()); + $success($this->ql,$response, $index); + }); + return $this; + } + + public function error(Closure $error) + { + $this->multiRequest = $this->multiRequest->error(function(RequestException $reason, $index) use($error){ + $error($this->ql,$reason, $index); + }); + return $this; + } + + public function send() + { + $this->multiRequest->{$this->method}(); + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/src/Services/PluginService.php b/vendor/jaeger/querylist/src/Services/PluginService.php new file mode 100644 index 0000000..e44fbe5 --- /dev/null +++ b/vendor/jaeger/querylist/src/Services/PluginService.php @@ -0,0 +1,26 @@ + + * Date: 2017/9/22 + */ + +namespace QL\Services; + +use QL\QueryList; + +class PluginService +{ + public static function install(QueryList $queryList, $plugins, ...$opt) + { + if(is_array($plugins)) + { + foreach ($plugins as $plugin) { + $plugin::install($queryList); + } + }else{ + $plugins::install($queryList,...$opt); + } + return $queryList; + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/tests/Dom/FindTest.php b/vendor/jaeger/querylist/tests/Dom/FindTest.php new file mode 100644 index 0000000..024df21 --- /dev/null +++ b/vendor/jaeger/querylist/tests/Dom/FindTest.php @@ -0,0 +1,71 @@ +html = $this->getSnippet('snippet-1'); + $this->ql = QueryList::html($this->html); + } + + /** + * @test + */ + public function find_first_dom_attr() + { + $img = []; + $img[] = $this->ql->find('img')->attr('src'); + $img[] = $this->ql->find('img')->src; + $img[] = $this->ql->find('img:eq(0)')->src; + $img[] = $this->ql->find('img')->eq(0)->src; + + $alt = $this->ql->find('img')->alt; + $abc = $this->ql->find('img')->abc; + + $this->assertCount(1,array_unique($img)); + $this->assertEquals($alt,'这是图片'); + $this->assertEquals($abc,'这是一个自定义属性'); + + } + + /** + * @test + */ + public function find_second_dom_attr() + { + + $img2 = []; + $img2[] = $this->ql->find('img')->eq(1)->alt; + $img2[] = $this->ql->find('img:eq(1)')->alt; + $img2[] = $this->ql->find('.second_pic')->alt; + + $this->assertCount(1,array_unique($img2)); + + } + + /** + * @test + */ + public function find_dom_all_attr() + { + $imgAttr = $this->ql->find('img:eq(0)')->attr('*'); + $linkAttr = $this->ql->find('a:eq(1)')->attr('*'); + $this->assertCount(3,$imgAttr); + $this->assertCount(1,$linkAttr); + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/tests/Dom/RulesTest.php b/vendor/jaeger/querylist/tests/Dom/RulesTest.php new file mode 100644 index 0000000..7c555ca --- /dev/null +++ b/vendor/jaeger/querylist/tests/Dom/RulesTest.php @@ -0,0 +1,43 @@ + + * Date: 18/12/12 + * Time: 下午12:25 + */ + +namespace Tests\Dom; + + +use QL\QueryList; +use Tests\TestCaseBase; +use Tightenco\Collect\Support\Collection; + +class RulesTest extends TestCaseBase +{ + protected $html; + protected $ql; + + protected function setUp(): void + { + $this->html = $this->getSnippet('snippet-2'); + $this->ql = QueryList::html($this->html); + } + + /** + * @test + */ + public function get_data_by_rules() + { + $rules = [ + 'a' => ['a','text'], + 'img_src' => ['img','src'], + 'img_alt' => ['img','alt'] + ]; + $range = 'ul>li'; + $data = QueryList::rules($rules)->range($range)->html($this->html)->query()->getData(); + $this->assertInstanceOf(Collection::class,$data); + $this->assertCount(3,$data); + $this->assertEquals('http://querylist.com/2.jpg',$data[1]['img_src']); + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/tests/Feature/HttpTest.php b/vendor/jaeger/querylist/tests/Feature/HttpTest.php new file mode 100644 index 0000000..0d723ed --- /dev/null +++ b/vendor/jaeger/querylist/tests/Feature/HttpTest.php @@ -0,0 +1,103 @@ +urls = [ + 'http://httpbin.org/get?name=php', + 'http://httpbin.org/get?name=golang', + 'http://httpbin.org/get?name=c++', + 'http://httpbin.org/get?name=java' + ]; + } + + /** + * @test + */ + public function can_post_json_data() + { + $mock = new MockHandler([new Response()]); + $data = [ + 'name' => 'foo' + ]; + QueryList::postJson('http://foo.com',$data,[ + 'handler' => $mock + ]); + $this->assertEquals((string)$mock->getLastRequest()->getBody(),json_encode($data)); + } + + /** + * @test + */ + public function concurrent_requests_base_use() + { + $urls = $this->urls; + QueryList::getInstance() + ->multiGet($urls) + ->success(function(QueryList $ql,Response $response, $index) use($urls){ + $body = json_decode((string)$response->getBody(),true); + $this->assertEquals($urls[$index],$body['url']); + })->send(); + } + + /** + * @test + */ + public function concurrent_requests_advanced_use() + { + $ua = 'QueryList/4.0'; + + $errorUrl = 'http://web-site-not-exist.com'; + $urls = array_merge($this->urls,[$errorUrl]); + + QueryList::rules([]) + ->multiGet($urls) + ->concurrency(2) + ->withOptions([ + 'timeout' => 60 + ]) + ->withHeaders([ + 'User-Agent' => $ua + ]) + ->success(function (QueryList $ql, Response $response, $index) use($ua){ + $body = json_decode((string)$response->getBody(),true); + $this->assertEquals($ua,$body['headers']['User-Agent']); + }) + ->error(function (QueryList $ql, $reason, $index) use($urls,$errorUrl){ + $this->assertEquals($urls[$index],$errorUrl); + }) + ->send(); + } + + /** + * @test + */ + public function request_with_cache() + { + $url = $this->urls[0]; + $data = QueryList::get($url,null,[ + 'cache' => sys_get_temp_dir(), + 'cache_ttl' => 600 + ])->getHtml(); + $data = json_decode($data,true); + $this->assertEquals($url,$data['url']); + + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/tests/Feature/InstanceTest.php b/vendor/jaeger/querylist/tests/Feature/InstanceTest.php new file mode 100644 index 0000000..a659060 --- /dev/null +++ b/vendor/jaeger/querylist/tests/Feature/InstanceTest.php @@ -0,0 +1,48 @@ +html = $this->getSnippet('snippet-1'); + } + /** + * @test + */ + public function singleton_instance_mode() + { + $ql = QueryList::getInstance()->html($this->html); + $ql2 = QueryList::getInstance(); + $this->assertEquals($ql->getHtml(),$ql2->getHtml()); + + + } + + /** + * @test + */ + public function get_new_object() + { + $ql = (new QueryList())->html($this->html); + $ql2 = (new QueryList())->html(''); + $this->assertNotEquals($ql->getHtml(),$ql2->getHtml()); + + $ql = QueryList::range('')->html($this->html); + $ql2 = QueryList::range('')->html(''); + $this->assertNotEquals($ql->getHtml(),$ql2->getHtml()); + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/tests/Feature/MethodTest.php b/vendor/jaeger/querylist/tests/Feature/MethodTest.php new file mode 100644 index 0000000..ca10cf9 --- /dev/null +++ b/vendor/jaeger/querylist/tests/Feature/MethodTest.php @@ -0,0 +1,36 @@ +html = $this->getSnippet('snippet-1'); + } + + /** + * @test + */ + public function pipe() + { + $html = $this->html; + $qlHtml = QueryList::pipe(function(QueryList $ql) use($html){ + $ql->setHtml($html); + return $ql; + })->getHtml(false); + $this->assertEquals($html,$qlHtml); + } +} \ No newline at end of file diff --git a/vendor/jaeger/querylist/tests/TestCaseBase.php b/vendor/jaeger/querylist/tests/TestCaseBase.php new file mode 100644 index 0000000..c88d73e --- /dev/null +++ b/vendor/jaeger/querylist/tests/TestCaseBase.php @@ -0,0 +1,20 @@ + + + 其它的一些文本 +
\ No newline at end of file diff --git a/vendor/jaeger/querylist/tests/assets/snippet-2.html b/vendor/jaeger/querylist/tests/assets/snippet-2.html new file mode 100644 index 0000000..88ae3f0 --- /dev/null +++ b/vendor/jaeger/querylist/tests/assets/snippet-2.html @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/vendor/jaeger/querylist/tests/bootstrap.php b/vendor/jaeger/querylist/tests/bootstrap.php new file mode 100644 index 0000000..eace7af --- /dev/null +++ b/vendor/jaeger/querylist/tests/bootstrap.php @@ -0,0 +1,5 @@ +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((string) $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..25d949e --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php @@ -0,0 +1,705 @@ +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') { + $result = compact('type', 'path'); + if ($this->enableTimestampsOnUnixListings) { + $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear); + $result += compact('timestamp'); + } + + return $result; + } + + $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}/', trim($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) + { + if (is_numeric($permissions)) { + return ((int) $permissions) & 0777; + } + + // 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() + { + if ( ! $this->isConnected()) { + $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(); + + protected function escapePath($path) + { + return str_replace(['*', '[', ']'], ['\\*', '\\[', '\\]'], $path); + } +} 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() + { + $tries = 3; + start_connecting: + + 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) { + $tries--; + + if ($tries > 0) goto start_connecting; + + throw new ConnectionRuntimeException('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 (!in_array(substr($response[0], 0, 3), ['200', '202'])) { + throw new ConnectionRuntimeException( + 'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + } + + /** + * Set the connections to passive mode. + * + * @throws ConnectionRuntimeException + */ + 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 ConnectionRuntimeException( + '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 InvalidRootException('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 ConnectionRuntimeException + */ + 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 ConnectionRuntimeException( + 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort( + ) . ', username: ' . $this->getUsername() + ); + } + } + + /** + * Disconnect from the FTP server. + */ + public function disconnect() + { + if ($this->hasFtpConnection()) { + @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', $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) + { + 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 ConnectionErrorException + */ + public function isConnected() + { + return $this->hasFtpConnection() && $this->getRawExecResponseCode('NOOP') === 200; + } + + /** + * @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 . ' ' . $this->escapePath($path)); + } + + private function getRawExecResponseCode($command) + { + $response = @ftp_raw($this->connection, trim($command)) ?: []; + + return (int) preg_replace('/\D/', '', implode(' ', (array) $response)); + } + + private function hasFtpConnection(): bool + { + return is_resource($this->connection) || $this->connection instanceof \FTP\Connection; + } +} diff --git a/vendor/league/flysystem/src/Adapter/Ftpd.php b/vendor/league/flysystem/src/Adapter/Ftpd.php new file mode 100644 index 0000000..7e71d19 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Ftpd.php @@ -0,0 +1,48 @@ + 'dir', 'path' => '']; + } + + if (@ftp_chdir($this->getConnection(), $path) === true) { + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $path]; + } + + $object = ftp_raw($this->getConnection(), 'STAT ' . $this->escapePath($path)); + + if ( ! $object || 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(), $this->escapePath($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..747c463 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Local.php @@ -0,0 +1,533 @@ + [ + '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 ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + $result['visibility'] = $visibility; + } + + 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/ConnectionErrorException.php b/vendor/league/flysystem/src/ConnectionErrorException.php new file mode 100644 index 0000000..adb651d --- /dev/null +++ b/vendor/league/flysystem/src/ConnectionErrorException.php @@ -0,0 +1,9 @@ +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..4596c0a --- /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..c4eaf27 --- /dev/null +++ b/vendor/league/flysystem/src/Filesystem.php @@ -0,0 +1,409 @@ +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) || get_resource_type($resource) !== 'stream') { + 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) || get_resource_type($resource) !== 'stream') { + 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) || get_resource_type($resource) !== 'stream') { + 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 (int) $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/FilesystemException.php b/vendor/league/flysystem/src/FilesystemException.php new file mode 100644 index 0000000..3121e53 --- /dev/null +++ b/vendor/league/flysystem/src/FilesystemException.php @@ -0,0 +1,7 @@ +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/InvalidRootException.php b/vendor/league/flysystem/src/InvalidRootException.php new file mode 100644 index 0000000..468d1d5 --- /dev/null +++ b/vendor/league/flysystem/src/InvalidRootException.php @@ -0,0 +1,9 @@ + 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..e0a989b --- /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..2f13d2f --- /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..0889d1f --- /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..d64debe --- /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..1a2db71 --- /dev/null +++ b/vendor/league/flysystem/src/Util.php @@ -0,0 +1,354 @@ + '']; + } + + /** + * 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; + } + } + + $path = implode('/', $parts); + + return $path; + } + + /** + * Rejects unprintable characters and invalid unicode characters. + * + * @param string $path + * + * @return string $path + */ + protected static function removeFunkyWhiteSpace($path) + { + if (preg_match('#\p{C}+#u', $path)) { + throw CorruptedPathDetected::forPath($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) { + [$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|null stream size + */ + public static function getStreamSize($resource) + { + $stat = fstat($resource); + + if ( ! is_array($stat) || ! isset($stat['size'])) { + return null; + } + + 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..35cba3f --- /dev/null +++ b/vendor/league/flysystem/src/Util/MimeType.php @@ -0,0 +1,80 @@ +detectMimeTypeFromBuffer($content); + } + + return 'text/plain'; + } + + /** + * Detects MIME Type based on file extension. + * + * @param string $extension + * + * @return string MIME Type + */ + public static function detectByFileExtension($extension) + { + return static::detector()->detectMimeTypeFromPath('artificial.' . $extension) ?: 'text/plain'; + } + + /** + * @param string $filename + * + * @return string MIME Type + */ + public static function detectByFilename($filename) + { + return static::detector()->detectMimeTypeFromPath($filename) ?: 'text/plain'; + } + + /** + * @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/league/mime-type-detection/CHANGELOG.md b/vendor/league/mime-type-detection/CHANGELOG.md new file mode 100644 index 0000000..2264f7a --- /dev/null +++ b/vendor/league/mime-type-detection/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +## 1.10.0 - 2022-04-11 + +### Fixed + +- Added Flysystem v1 inconclusive mime-types and made it configurable as a constructor parameter. + +## 1.9.0 - 2021-11-21 + +### Updated + +- Updated lookup + +## 1.8.0 - 2021-09-25 + +### Added + +- Added the decorator `OverridingExtensionToMimeTypeMap` which allows you to override values. + +## 1.7.0 - 2021-01-18 + +### Added + +- Added a `bufferSampleSize` parameter to the `FinfoMimeTypeDetector` class that allows you to send a reduced content sample which costs less memory. + +## 1.6.0 - 2021-01-18 + +### Changes + +- Updated generated mime-type map diff --git a/vendor/league/mime-type-detection/LICENSE b/vendor/league/mime-type-detection/LICENSE new file mode 100644 index 0000000..1f01652 --- /dev/null +++ b/vendor/league/mime-type-detection/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2022 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/mime-type-detection/composer.json b/vendor/league/mime-type-detection/composer.json new file mode 100644 index 0000000..80ca1af --- /dev/null +++ b/vendor/league/mime-type-detection/composer.json @@ -0,0 +1,34 @@ +{ + "name": "league/mime-type-detection", + "description": "Mime-type detection for Flysystem", + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "scripts": { + "test": "vendor/bin/phpunit", + "phpstan": "vendor/bin/phpstan analyse -l 6 src" + }, + "require": { + "php": "^7.2 || ^8.0", + "ext-fileinfo": "*" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.8 || ^9.3", + "phpstan/phpstan": "^0.12.68", + "friendsofphp/php-cs-fixer": "^3.2" + }, + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "config": { + "platform": { + "php": "7.2.0" + } + } +} diff --git a/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php new file mode 100644 index 0000000..fc04241 --- /dev/null +++ b/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php @@ -0,0 +1,13 @@ +extensions = $extensions ?: new GeneratedExtensionToMimeTypeMap(); + } + + public function detectMimeType(string $path, $contents): ?string + { + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromPath(string $path): ?string + { + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + return $this->extensions->lookupMimeType($extension); + } + + public function detectMimeTypeFromFile(string $path): ?string + { + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromBuffer(string $contents): ?string + { + return null; + } +} diff --git a/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php new file mode 100644 index 0000000..1dad7bc --- /dev/null +++ b/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php @@ -0,0 +1,10 @@ + + */ + private $inconclusiveMimetypes; + + public function __construct( + string $magicFile = '', + ExtensionToMimeTypeMap $extensionMap = null, + ?int $bufferSampleSize = null, + array $inconclusiveMimetypes = self::INCONCLUSIVE_MIME_TYPES + ) { + $this->finfo = new finfo(FILEINFO_MIME_TYPE, $magicFile); + $this->extensionMap = $extensionMap ?: new GeneratedExtensionToMimeTypeMap(); + $this->bufferSampleSize = $bufferSampleSize; + $this->inconclusiveMimetypes = $inconclusiveMimetypes; + } + + public function detectMimeType(string $path, $contents): ?string + { + $mimeType = is_string($contents) + ? (@$this->finfo->buffer($this->takeSample($contents)) ?: null) + : null; + + if ($mimeType !== null && ! in_array($mimeType, $this->inconclusiveMimetypes)) { + return $mimeType; + } + + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromPath(string $path): ?string + { + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + return $this->extensionMap->lookupMimeType($extension); + } + + public function detectMimeTypeFromFile(string $path): ?string + { + return @$this->finfo->file($path) ?: null; + } + + public function detectMimeTypeFromBuffer(string $contents): ?string + { + return @$this->finfo->buffer($this->takeSample($contents)) ?: null; + } + + private function takeSample(string $contents): string + { + if ($this->bufferSampleSize === null) { + return $contents; + } + + return (string) substr($contents, 0, $this->bufferSampleSize); + } +} diff --git a/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php new file mode 100644 index 0000000..f092388 --- /dev/null +++ b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php @@ -0,0 +1,1227 @@ + 'application/vnd.1000minds.decision-model+xml', + '3dml' => 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + '3gpp' => 'video/3gpp', + '3mf' => 'model/3mf', + '7z' => 'application/x-7z-compressed', + '7zip' => 'application/x-7z-compressed', + '123' => 'application/vnd.lotus-1-2-3', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-acc', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac3' => 'audio/ac3', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'age' => 'application/vnd.age', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/pdf', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'amr' => 'audio/amr', + 'apk' => 'application/vnd.android.package-archive', + 'apng' => 'image/apng', + 'appcache' => 'text/cache-manifest', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'arj' => 'application/x-arj', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomdeleted' => 'application/atomdeleted+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/x-au', + 'avci' => 'image/avci', + 'avcs' => 'image/avcs', + 'avi' => 'video/x-msvideo', + 'avif' => 'image/avif', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azv' => 'image/vnd.airzip.accelerator.azv', + 'azw' => 'application/vnd.amazon.ebook', + 'b16' => 'image/vnd.pco.b16', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bdoc' => 'application/x-bdoc', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmml' => 'application/vnd.balsamiq.bmml+xml', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btif' => 'image/prs.btif', + 'buffer' => 'application/octet-stream', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cco' => 'application/x-cocoa', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdfx' => 'application/cdfx+xml', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdr' => 'application/cdr', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cjs' => 'application/node', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/octet-stream', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'coffee' => 'text/coffeescript', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpl' => 'application/cpl+xml', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'crx' => 'application/x-chrome-extension', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csl' => 'application/vnd.citationstyles.style+xml', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'csr' => 'application/octet-stream', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbf' => 'application/vnd.dbf', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'ddf' => 'application/vnd.syncml.dmddf+xml', + 'dds' => 'image/vnd.ms-dds', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'disposition-notification' => 'message/disposition-notification', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/x-apple-diskimage', + 'dmn' => 'application/octet-stream', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'drle' => 'image/dicom-rle', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwd' => 'application/atsc-dwd+xml', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ear' => 'application/java-archive', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'image/emf', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emotionml' => 'application/emotionml+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es' => 'application/ecmascript', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'exi' => 'application/exi', + 'exp' => 'application/express', + 'exr' => 'image/aces', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/mp4', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fdt' => 'application/fdt+xml', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fits' => 'image/fits', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'fo' => 'application/vnd.software602.filler.form+xml', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'gdoc' => 'application/vnd.google-apps.document', + 'ged' => 'text/vnd.familysearch.gedcom', + 'geo' => 'application/vnd.dynageo', + 'geojson' => 'application/geo+json', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'glb' => 'model/gltf-binary', + 'gltf' => 'model/gltf+json', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gpg' => 'application/gpg-keys', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gslides' => 'application/vnd.google-apps.presentation', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/gzip', + 'gzip' => 'application/gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hbs' => 'text/x-handlebars-template', + 'hdd' => 'application/x-virtualbox-hdd', + 'hdf' => 'application/x-hdf', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'hej2' => 'image/hej2k', + 'held' => 'application/atsc-held+xml', + 'hh' => 'text/x-c', + 'hjson' => 'application/hjson', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hsj2' => 'image/hsj2', + 'htc' => 'text/x-component', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'img' => 'application/octet-stream', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'its' => 'application/its+xml', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jade' => 'text/jade', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'jardiff' => 'application/x-java-archive-diff', + 'java' => 'text/x-java-source', + 'jhc' => 'image/jphc', + 'jisp' => 'application/vnd.jisp', + 'jls' => 'image/jls', + 'jlt' => 'application/vnd.hp-jlyt', + 'jng' => 'image/x-jng', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpf' => 'image/jpx', + 'jpg' => 'image/jpeg', + 'jpg2' => 'image/jp2', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jph' => 'image/jph', + 'jpm' => 'video/jpm', + 'jpx' => 'image/jpx', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'json5' => 'application/json5', + 'jsonld' => 'application/ld+json', + 'jsonml' => 'application/jsonml+json', + 'jsx' => 'text/jsx', + 'jxr' => 'image/jxr', + 'jxra' => 'image/jxra', + 'jxrs' => 'image/jxrs', + 'jxs' => 'image/jxs', + 'jxsc' => 'image/jxsc', + 'jxsi' => 'image/jxsi', + 'jxss' => 'image/jxss', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kdb' => 'application/octet-stream', + 'kdbx' => 'application/x-keepass2', + 'key' => 'application/x-iwork-keynote-sffkey', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktx2' => 'image/ktx2', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'less' => 'text/less', + 'lgr' => 'application/lgr+xml', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'litcoffee' => 'text/coffeescript', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lua' => 'text/x-lua', + 'luac' => 'application/x-lua-bytecode', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'text/plain', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/x-m4a', + 'm4p' => 'application/mp4', + 'm4s' => 'video/iso.segment', + 'm4u' => 'application/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm21' => 'application/mp21', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'maei' => 'application/mmt-aei+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'manifest' => 'text/cache-manifest', + 'map' => 'application/json', + 'mar' => 'application/octet-stream', + 'markdown' => 'text/markdown', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'md' => 'text/markdown', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'mdx' => 'text/mdx', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mjs' => 'application/javascript', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mkd' => 'text/x-markdown', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mml' => 'text/mathml', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mp21' => 'application/mp21', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpd' => 'application/dash+xml', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpf' => 'application/media-policy-dataset+xml', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msg' => 'application/vnd.ms-outlook', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msm' => 'application/octet-stream', + 'msp' => 'application/octet-stream', + 'msty' => 'application/vnd.muvee.style', + 'mtl' => 'model/mtl', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musd' => 'application/mmt-usd+xml', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mvt' => 'application/vnd.mapbox-vector-tile', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxmf' => 'audio/mobile-xmf', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nq' => 'application/n-quads', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'nt' => 'application/n-triples', + 'ntf' => 'application/vnd.nitf', + 'numbers' => 'application/x-iwork-numbers-sffnumbers', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obgx' => 'application/vnd.openblox.game+xml', + 'obj' => 'model/obj', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogex' => 'model/vnd.opengex', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'opus' => 'audio/ogg', + 'org' => 'text/x-org', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'osm' => 'application/vnd.openstreetmap.data+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'font/otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ova' => 'application/x-virtualbox-ova', + 'ovf' => 'application/x-virtualbox-ovf', + 'owl' => 'application/rdf+xml', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p7a' => 'application/x-pkcs7-signature', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'pages' => 'application/x-iwork-pages-sffpages', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/x-pilot', + 'pde' => 'text/x-processing', + 'pdf' => 'application/pdf', + 'pem' => 'application/x-x509-user-cert', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp', + 'phar' => 'application/octet-stream', + 'php' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'phtml' => 'application/x-httpd-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'pkpass' => 'application/vnd.apple.pkpass', + 'pl' => 'application/x-perl', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pm' => 'application/x-perl', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'model/prc', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'provx' => 'application/provenance+xml', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'application/x-photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'pti' => 'image/prs.pti', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'raml' => 'application/raml+yaml', + 'rapd' => 'application/route-apd+xml', + 'rar' => 'application/x-rar', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'relo' => 'application/p2p-overlay+xml', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rng' => 'application/xml', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsa' => 'application/x-pkcs7', + 'rsat' => 'application/atsc-rsat+xml', + 'rsd' => 'application/rsd+xml', + 'rsheet' => 'application/urc-ressheet+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'run' => 'application/x-makeself', + 'rusd' => 'application/route-usd+xml', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sass' => 'text/x-sass', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scss' => 'text/x-scss', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'sea' => 'application/octet-stream', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'senmlx' => 'application/senml+xml', + 'sensmlx' => 'application/sensml+xml', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shex' => 'text/shex', + 'shf' => 'application/shf+xml', + 'shtml' => 'text/html', + 'sid' => 'image/x-mrsid-image', + 'sieve' => 'application/sieve', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'siv' => 'application/sieve', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slim' => 'text/slim', + 'slm' => 'text/slim', + 'sls' => 'application/route-s-tsid+xml', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spdx' => 'text/spdx', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'sst' => 'application/octet-stream', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'model/stl', + 'stpx' => 'model/step+xml', + 'stpxz' => 'model/step-xml+zip', + 'stpz' => 'model/step+zip', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'styl' => 'text/stylus', + 'stylus' => 'text/stylus', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'swidtag' => 'application/swid+xml', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 't38' => 'image/t38', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tap' => 'image/vnd.tencent.tap', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'td' => 'application/urc-targetdesc+xml', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tfx' => 'image/tiff-fx', + 'tga' => 'image/x-tga', + 'tgz' => 'application/x-tar', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tk' => 'application/x-tcl', + 'tmo' => 'application/vnd.tmobile-livetv', + 'toml' => 'application/toml', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trig' => 'application/trig', + 'trm' => 'application/x-msterminal', + 'ts' => 'video/mp2t', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'font/collection', + 'ttf' => 'font/ttf', + 'ttl' => 'text/turtle', + 'ttml' => 'application/ttml+xml', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u3d' => 'model/u3d', + 'u8dsn' => 'message/global-delivery-status', + 'u8hdr' => 'message/global-headers', + 'u8mdn' => 'message/global-disposition-notification', + 'u8msg' => 'message/global', + 'u32' => 'application/x-authorware-bin', + 'ubj' => 'application/ubjson', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'usdz' => 'model/vnd.usdz+zip', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vbox' => 'application/x-virtualbox-vbox', + 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vdi' => 'application/x-virtualbox-vdi', + 'vds' => 'model/vnd.sap.vds', + 'vhd' => 'application/x-virtualbox-vhd', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vlc' => 'application/videolan', + 'vmdk' => 'application/x-virtualbox-vmdk', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtf' => 'image/vnd.valve.source.texture', + 'vtt' => 'text/vtt', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wadl' => 'application/vnd.sun.wadl+xml', + 'war' => 'application/java-archive', + 'wasm' => 'application/wasm', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webapp' => 'application/x-web-app-manifest+json', + 'webm' => 'video/webm', + 'webmanifest' => 'application/manifest+json', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wif' => 'application/watcherinfo+xml', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'image/wmf', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + 'word' => 'application/msword', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsc' => 'message/vnd.wfa.wsc', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+fastinfoset', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d-vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'x32' => 'application/x-authorware-bin', + 'x_b' => 'model/vnd.parasolid.transmit.binary', + 'x_t' => 'model/vnd.parasolid.transmit.text', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xav' => 'application/xcap-att+xml', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xca' => 'application/xcap-caps+xml', + 'xcs' => 'application/calendar+xml', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xel' => 'application/xcap-el+xml', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xns' => 'application/xcap-ns+xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsd' => 'application/xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'ymp' => 'text/x-suse-ymp', + 'z' => 'application/x-compress', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'zsh' => 'text/x-scriptzsh', + ]; + + public function lookupMimeType(string $extension): ?string + { + return self::MIME_TYPES_FOR_EXTENSIONS[$extension] ?? null; + } +} diff --git a/vendor/league/mime-type-detection/src/MimeTypeDetector.php b/vendor/league/mime-type-detection/src/MimeTypeDetector.php new file mode 100644 index 0000000..5d799d2 --- /dev/null +++ b/vendor/league/mime-type-detection/src/MimeTypeDetector.php @@ -0,0 +1,19 @@ + $overrides + */ + public function __construct(ExtensionToMimeTypeMap $innerMap, array $overrides) + { + $this->innerMap = $innerMap; + $this->overrides = $overrides; + } + + public function lookupMimeType(string $extension): ?string + { + return $this->overrides[$extension] ?? $this->innerMap->lookupMimeType($extension); + } +} 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 @@ +=7.0.0", + "psr/http-message": "^1.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/http-factory/src/RequestFactoryInterface.php b/vendor/psr/http-factory/src/RequestFactoryInterface.php new file mode 100644 index 0000000..cb39a08 --- /dev/null +++ b/vendor/psr/http-factory/src/RequestFactoryInterface.php @@ -0,0 +1,18 @@ +=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/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..e02f9da --- /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 mixed[] $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 mixed[] $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 mixed[] $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 mixed[] $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param mixed[] $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 mixed[] $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param mixed[] $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..2206cfd --- /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/DummyTest.php b/vendor/psr/log/Psr/Log/Test/DummyTest.php new file mode 100644 index 0000000..9638c11 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/DummyTest.php @@ -0,0 +1,18 @@ + ". + * + * 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()); + } +} 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..ca05695 --- /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": "https://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/symfony/deprecation-contracts/.gitignore b/vendor/symfony/deprecation-contracts/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/deprecation-contracts/CHANGELOG.md b/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..406242f --- /dev/null +++ b/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2022 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/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..4957933 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/vendor/symfony/deprecation-contracts/composer.json b/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000..cc7cc12 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/deprecation-contracts/function.php b/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..d437150 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} 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..bce5c4a --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,874 @@ + + * + * 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 +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || (null !== $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]+/', [__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, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::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) || (80000 > \PHP_VERSION_ID && !$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 self::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) || (80000 > \PHP_VERSION_ID && !$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 = ["\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, [__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::CASE_FOLD[0], self::CASE_FOLD[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\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; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['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, [$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) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + 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 = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $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) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + 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, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $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 (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + 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) { + $pos = strrpos($haystack, $needle); + } else { + $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 = [ + '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..478b40d --- /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](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/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..fac60b0 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + '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', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..1fedd1f --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,147 @@ + + * + * 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 (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..82f5ac4 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ + + * + * 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_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..4489553 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,41 @@ +{ + "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": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000..5593b1d --- /dev/null +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 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-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..362dd1a --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/vendor/symfony/polyfill-php80/PhpToken.php b/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000..fe6e691 --- /dev/null +++ b/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000..3816c55 --- /dev/null +++ b/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000..2b95542 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000..bd1212f --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000..7c62d75 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 0000000..01c6c6c --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 0000000..783dbc2 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/bootstrap.php b/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 0000000..e5f7dbc --- /dev/null +++ b/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/vendor/symfony/polyfill-php80/composer.json b/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000..bd9a326 --- /dev/null +++ b/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/var-dumper/CHANGELOG.md b/vendor/symfony/var-dumper/CHANGELOG.md new file mode 100644 index 0000000..f58ed31 --- /dev/null +++ b/vendor/symfony/var-dumper/CHANGELOG.md @@ -0,0 +1,72 @@ +CHANGELOG +========= + +5.4 +--- + + * Add ability to style integer and double values independently + * Add casters for Symfony's UUIDs and ULIDs + * Add support for `Fiber` + +5.2.0 +----- + + * added support for PHPUnit `--colors` option + * added `VAR_DUMPER_FORMAT=server` env var value support + * prevent replacing the handler when the `VAR_DUMPER_FORMAT` env var is set + +5.1.0 +----- + + * added `RdKafka` support + +4.4.0 +----- + + * added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()` + to configure casters & flags to use in tests + * added `ImagineCaster` and infrastructure to dump images + * added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data + * added `UuidCaster` + * made all casters final + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added `DsCaster` to support dumping the contents of data structures from the Ds extension + +4.2.0 +----- + + * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli` + +4.1.0 +----- + + * added a `ServerDumper` to send serialized Data clones to a server + * added a `ServerDumpCommand` and `DumpServer` to run a server collecting + and displaying dumps on a single place with multiple formats support + * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support + +4.0.0 +----- + + * support for passing `\ReflectionClass` instances to the `Caster::castObject()` + method has been dropped, pass class names as strings instead + * the `Data::getRawData()` method has been removed + * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + +3.4.0 +----- + + * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth + * deprecated `MongoCaster` + +2.7.0 +----- + + * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead. diff --git a/vendor/symfony/var-dumper/Caster/AmqpCaster.php b/vendor/symfony/var-dumper/Caster/AmqpCaster.php new file mode 100644 index 0000000..dc3b621 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/AmqpCaster.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Amqp related classes to array representation. + * + * @author Grégoire Pineau + * + * @final + */ +class AmqpCaster +{ + private const FLAGS = [ + \AMQP_DURABLE => 'AMQP_DURABLE', + \AMQP_PASSIVE => 'AMQP_PASSIVE', + \AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE', + \AMQP_AUTODELETE => 'AMQP_AUTODELETE', + \AMQP_INTERNAL => 'AMQP_INTERNAL', + \AMQP_NOLOCAL => 'AMQP_NOLOCAL', + \AMQP_AUTOACK => 'AMQP_AUTOACK', + \AMQP_IFEMPTY => 'AMQP_IFEMPTY', + \AMQP_IFUNUSED => 'AMQP_IFUNUSED', + \AMQP_MANDATORY => 'AMQP_MANDATORY', + \AMQP_IMMEDIATE => 'AMQP_IMMEDIATE', + \AMQP_MULTIPLE => 'AMQP_MULTIPLE', + \AMQP_NOWAIT => 'AMQP_NOWAIT', + \AMQP_REQUEUE => 'AMQP_REQUEUE', + ]; + + private const EXCHANGE_TYPES = [ + \AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT', + \AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT', + \AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC', + \AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS', + ]; + + public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPConnection\x00login"])) { + return $a; + } + + // BC layer in the amqp lib + if (method_exists($c, 'getReadTimeout')) { + $timeout = $c->getReadTimeout(); + } else { + $timeout = $c->getTimeout(); + } + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'login' => $c->getLogin(), + $prefix.'password' => $c->getPassword(), + $prefix.'host' => $c->getHost(), + $prefix.'vhost' => $c->getVhost(), + $prefix.'port' => $c->getPort(), + $prefix.'read_timeout' => $timeout, + ]; + + return $a; + } + + public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'channel_id' => $c->getChannelId(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPChannel\x00connection"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'prefetch_size' => $c->getPrefetchSize(), + $prefix.'prefetch_count' => $c->getPrefetchCount(), + ]; + + return $a; + } + + public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPQueue\x00name"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + $type = isset(self::EXCHANGE_TYPES[$c->getType()]) ? new ConstStub(self::EXCHANGE_TYPES[$c->getType()], $c->getType()) : $c->getType(); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPExchange\x00name"])) { + $a["\x00AMQPExchange\x00type"] = $type; + + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'type' => $type, + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPEnvelope\x00body"])) { + $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode; + + return $a; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $a += [$prefix.'body' => $c->getBody()]; + } + + $a += [ + $prefix.'delivery_tag' => $c->getDeliveryTag(), + $prefix.'is_redelivery' => $c->isRedelivery(), + $prefix.'exchange_name' => $c->getExchangeName(), + $prefix.'routing_key' => $c->getRoutingKey(), + $prefix.'content_type' => $c->getContentType(), + $prefix.'content_encoding' => $c->getContentEncoding(), + $prefix.'headers' => $c->getHeaders(), + $prefix.'delivery_mode' => $deliveryMode, + $prefix.'priority' => $c->getPriority(), + $prefix.'correlation_id' => $c->getCorrelationId(), + $prefix.'reply_to' => $c->getReplyTo(), + $prefix.'expiration' => $c->getExpiration(), + $prefix.'message_id' => $c->getMessageId(), + $prefix.'timestamp' => $c->getTimeStamp(), + $prefix.'type' => $c->getType(), + $prefix.'user_id' => $c->getUserId(), + $prefix.'app_id' => $c->getAppId(), + ]; + + return $a; + } + + private static function extractFlags(int $flags): ConstStub + { + $flagsArray = []; + + foreach (self::FLAGS as $value => $name) { + if ($flags & $value) { + $flagsArray[] = $name; + } + } + + if (!$flagsArray) { + $flagsArray = ['AMQP_NOPARAM']; + } + + return new ConstStub(implode('|', $flagsArray), $flags); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ArgsStub.php b/vendor/symfony/var-dumper/Caster/ArgsStub.php new file mode 100644 index 0000000..b3f7bbe --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ArgsStub.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a list of function arguments. + * + * @author Nicolas Grekas + */ +class ArgsStub extends EnumStub +{ + private static $parameters = []; + + public function __construct(array $args, string $function, ?string $class) + { + [$variadic, $params] = self::getParameters($function, $class); + + $values = []; + foreach ($args as $k => $v) { + $values[$k] = !\is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; + } + if (null === $params) { + parent::__construct($values, false); + + return; + } + if (\count($values) < \count($params)) { + $params = \array_slice($params, 0, \count($values)); + } elseif (\count($values) > \count($params)) { + $values[] = new EnumStub(array_splice($values, \count($params)), false); + $params[] = $variadic; + } + if (['...'] === $params) { + $this->dumpKeys = false; + $this->value = $values[0]->value; + } else { + $this->value = array_combine($params, $values); + } + } + + private static function getParameters(string $function, ?string $class): array + { + if (isset(self::$parameters[$k = $class.'::'.$function])) { + return self::$parameters[$k]; + } + + try { + $r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function); + } catch (\ReflectionException $e) { + return [null, null]; + } + + $variadic = '...'; + $params = []; + foreach ($r->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + if ($v->isVariadic()) { + $variadic .= $k; + } else { + $params[] = $k; + } + } + + return self::$parameters[$k] = [$variadic, $params]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/Caster.php b/vendor/symfony/var-dumper/Caster/Caster.php new file mode 100644 index 0000000..53f4461 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/Caster.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Helper for filtering out properties in casters. + * + * @author Nicolas Grekas + * + * @final + */ +class Caster +{ + public const EXCLUDE_VERBOSE = 1; + public const EXCLUDE_VIRTUAL = 2; + public const EXCLUDE_DYNAMIC = 4; + public const EXCLUDE_PUBLIC = 8; + public const EXCLUDE_PROTECTED = 16; + public const EXCLUDE_PRIVATE = 32; + public const EXCLUDE_NULL = 64; + public const EXCLUDE_EMPTY = 128; + public const EXCLUDE_NOT_IMPORTANT = 256; + public const EXCLUDE_STRICT = 512; + + public const PREFIX_VIRTUAL = "\0~\0"; + public const PREFIX_DYNAMIC = "\0+\0"; + public const PREFIX_PROTECTED = "\0*\0"; + + /** + * Casts objects to arrays and adds the dynamic property prefix. + * + * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not + */ + public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, string $debugClass = null): array + { + if ($hasDebugInfo) { + try { + $debugInfo = $obj->__debugInfo(); + } catch (\Exception $e) { + // ignore failing __debugInfo() + $hasDebugInfo = false; + } + } + + $a = $obj instanceof \Closure ? [] : (array) $obj; + + if ($obj instanceof \__PHP_Incomplete_Class) { + return $a; + } + + if ($a) { + static $publicProperties = []; + $debugClass = $debugClass ?? get_debug_type($obj); + + $i = 0; + $prefixedKeys = []; + foreach ($a as $k => $v) { + if ("\0" !== ($k[0] ?? '')) { + if (!isset($publicProperties[$class])) { + foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { + $publicProperties[$class][$prop->name] = true; + } + } + if (!isset($publicProperties[$class][$k])) { + $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k; + } + } elseif ($debugClass !== $class && 1 === strpos($k, $class)) { + $prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0"); + } + ++$i; + } + if ($prefixedKeys) { + $keys = array_keys($a); + foreach ($prefixedKeys as $i => $k) { + $keys[$i] = $k; + } + $a = array_combine($keys, $a); + } + } + + if ($hasDebugInfo && \is_array($debugInfo)) { + foreach ($debugInfo as $k => $v) { + if (!isset($k[0]) || "\0" !== $k[0]) { + if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) { + continue; + } + $k = self::PREFIX_VIRTUAL.$k; + } + + unset($a[$k]); + $a[$k] = $v; + } + } + + return $a; + } + + /** + * Filters out the specified properties. + * + * By default, a single match in the $filter bit field filters properties out, following an "or" logic. + * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. + * + * @param array $a The array containing the properties to filter + * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out + * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set + * @param int &$count Set to the number of removed properties + */ + public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array + { + $count = 0; + + foreach ($a as $k => $v) { + $type = self::EXCLUDE_STRICT & $filter; + + if (null === $v) { + $type |= self::EXCLUDE_NULL & $filter; + $type |= self::EXCLUDE_EMPTY & $filter; + } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) { + $type |= self::EXCLUDE_EMPTY & $filter; + } + if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_NOT_IMPORTANT; + } + if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_VERBOSE; + } + + if (!isset($k[1]) || "\0" !== $k[0]) { + $type |= self::EXCLUDE_PUBLIC & $filter; + } elseif ('~' === $k[1]) { + $type |= self::EXCLUDE_VIRTUAL & $filter; + } elseif ('+' === $k[1]) { + $type |= self::EXCLUDE_DYNAMIC & $filter; + } elseif ('*' === $k[1]) { + $type |= self::EXCLUDE_PROTECTED & $filter; + } else { + $type |= self::EXCLUDE_PRIVATE & $filter; + } + + if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) { + unset($a[$k]); + ++$count; + } + } + + return $a; + } + + public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array + { + if (isset($a['__PHP_Incomplete_Class_Name'])) { + $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')'; + unset($a['__PHP_Incomplete_Class_Name']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ClassStub.php b/vendor/symfony/var-dumper/Caster/ClassStub.php new file mode 100644 index 0000000..48f8483 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ClassStub.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP class identifier. + * + * @author Nicolas Grekas + */ +class ClassStub extends ConstStub +{ + /** + * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name + * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier + */ + public function __construct(string $identifier, $callable = null) + { + $this->value = $identifier; + + try { + if (null !== $callable) { + if ($callable instanceof \Closure) { + $r = new \ReflectionFunction($callable); + } elseif (\is_object($callable)) { + $r = [$callable, '__invoke']; + } elseif (\is_array($callable)) { + $r = $callable; + } elseif (false !== $i = strpos($callable, '::')) { + $r = [substr($callable, 0, $i), substr($callable, 2 + $i)]; + } else { + $r = new \ReflectionFunction($callable); + } + } elseif (0 < $i = strpos($identifier, '::') ?: strpos($identifier, '->')) { + $r = [substr($identifier, 0, $i), substr($identifier, 2 + $i)]; + } else { + $r = new \ReflectionClass($identifier); + } + + if (\is_array($r)) { + try { + $r = new \ReflectionMethod($r[0], $r[1]); + } catch (\ReflectionException $e) { + $r = new \ReflectionClass($r[0]); + } + } + + if (str_contains($identifier, "@anonymous\0")) { + $this->value = $identifier = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $identifier); + } + + if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) { + $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true, Caster::EXCLUDE_VERBOSE); + $s = ReflectionCaster::getSignature($s); + + if (str_ends_with($identifier, '()')) { + $this->value = substr_replace($identifier, $s, -2); + } else { + $this->value .= $s; + } + } + } catch (\ReflectionException $e) { + return; + } finally { + if (0 < $i = strrpos($this->value, '\\')) { + $this->attr['ellipsis'] = \strlen($this->value) - $i; + $this->attr['ellipsis-type'] = 'class'; + $this->attr['ellipsis-tail'] = 1; + } + } + + if ($f = $r->getFileName()) { + $this->attr['file'] = $f; + $this->attr['line'] = $r->getStartLine(); + } + } + + public static function wrapCallable($callable) + { + if (\is_object($callable) || !\is_callable($callable)) { + return $callable; + } + + if (!\is_array($callable)) { + $callable = new static($callable, $callable); + } elseif (\is_string($callable[0])) { + $callable[0] = new static($callable[0], $callable); + } else { + $callable[1] = new static($callable[1], $callable); + } + + return $callable; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ConstStub.php b/vendor/symfony/var-dumper/Caster/ConstStub.php new file mode 100644 index 0000000..8b01797 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ConstStub.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP constant and its value. + * + * @author Nicolas Grekas + */ +class ConstStub extends Stub +{ + public function __construct(string $name, $value = null) + { + $this->class = $name; + $this->value = 1 < \func_num_args() ? $value : $name; + } + + /** + * @return string + */ + public function __toString() + { + return (string) $this->value; + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutArrayStub.php b/vendor/symfony/var-dumper/Caster/CutArrayStub.php new file mode 100644 index 0000000..0e4fb36 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutArrayStub.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 Symfony\Component\VarDumper\Caster; + +/** + * Represents a cut array. + * + * @author Nicolas Grekas + */ +class CutArrayStub extends CutStub +{ + public $preservedSubset; + + public function __construct(array $value, array $preservedKeys) + { + parent::__construct($value); + + $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys)); + $this->cut -= \count($this->preservedSubset); + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutStub.php b/vendor/symfony/var-dumper/Caster/CutStub.php new file mode 100644 index 0000000..464c6db --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutStub.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents the main properties of a PHP variable, pre-casted by a caster. + * + * @author Nicolas Grekas + */ +class CutStub extends Stub +{ + public function __construct($value) + { + $this->value = $value; + + switch (\gettype($value)) { + case 'object': + $this->type = self::TYPE_OBJECT; + $this->class = \get_class($value); + + if ($value instanceof \Closure) { + ReflectionCaster::castClosure($value, [], $this, true, Caster::EXCLUDE_VERBOSE); + } + + $this->cut = -1; + break; + + case 'array': + $this->type = self::TYPE_ARRAY; + $this->class = self::ARRAY_ASSOC; + $this->cut = $this->value = \count($value); + break; + + case 'resource': + case 'unknown type': + case 'resource (closed)': + $this->type = self::TYPE_RESOURCE; + $this->handle = (int) $value; + if ('Unknown' === $this->class = @get_resource_type($value)) { + $this->class = 'Closed'; + } + $this->cut = -1; + break; + + case 'string': + $this->type = self::TYPE_STRING; + $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; + $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8'); + $this->value = ''; + break; + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/DOMCaster.php b/vendor/symfony/var-dumper/Caster/DOMCaster.php new file mode 100644 index 0000000..4dd16e0 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DOMCaster.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DOM related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class DOMCaster +{ + private const ERROR_CODES = [ + \DOM_PHP_ERR => 'DOM_PHP_ERR', + \DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', + \DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', + \DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR', + \DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR', + \DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR', + \DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR', + \DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR', + \DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR', + \DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR', + \DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR', + \DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR', + \DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR', + \DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR', + \DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR', + \DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR', + \DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', + ]; + + private const NODE_TYPES = [ + \XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', + \XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', + \XML_TEXT_NODE => 'XML_TEXT_NODE', + \XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE', + \XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE', + \XML_ENTITY_NODE => 'XML_ENTITY_NODE', + \XML_PI_NODE => 'XML_PI_NODE', + \XML_COMMENT_NODE => 'XML_COMMENT_NODE', + \XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE', + \XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE', + \XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE', + \XML_NOTATION_NODE => 'XML_NOTATION_NODE', + \XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE', + \XML_DTD_NODE => 'XML_DTD_NODE', + \XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE', + \XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE', + \XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE', + \XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE', + ]; + + public static function castException(\DOMException $e, array $a, Stub $stub, bool $isNested) + { + $k = Caster::PREFIX_PROTECTED.'code'; + if (isset($a[$k], self::ERROR_CODES[$a[$k]])) { + $a[$k] = new ConstStub(self::ERROR_CODES[$a[$k]], $a[$k]); + } + + return $a; + } + + public static function castLength($dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castImplementation(\DOMImplementation $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'Core' => '1.0', + Caster::PREFIX_VIRTUAL.'XML' => '2.0', + ]; + + return $a; + } + + public static function castNode(\DOMNode $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), + 'parentNode' => new CutStub($dom->parentNode), + 'childNodes' => $dom->childNodes, + 'firstChild' => new CutStub($dom->firstChild), + 'lastChild' => new CutStub($dom->lastChild), + 'previousSibling' => new CutStub($dom->previousSibling), + 'nextSibling' => new CutStub($dom->nextSibling), + 'attributes' => $dom->attributes, + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'namespaceURI' => $dom->namespaceURI, + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI, + 'textContent' => new CutStub($dom->textContent), + ]; + + return $a; + } + + public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'namespaceURI' => $dom->namespaceURI, + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'parentNode' => new CutStub($dom->parentNode), + ]; + + return $a; + } + + public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + 'doctype' => $dom->doctype, + 'implementation' => $dom->implementation, + 'documentElement' => new CutStub($dom->documentElement), + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'xmlEncoding' => $dom->xmlEncoding, + 'standalone' => $dom->standalone, + 'xmlStandalone' => $dom->xmlStandalone, + 'version' => $dom->version, + 'xmlVersion' => $dom->xmlVersion, + 'strictErrorChecking' => $dom->strictErrorChecking, + 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, + 'config' => $dom->config, + 'formatOutput' => $dom->formatOutput, + 'validateOnParse' => $dom->validateOnParse, + 'resolveExternals' => $dom->resolveExternals, + 'preserveWhiteSpace' => $dom->preserveWhiteSpace, + 'recover' => $dom->recover, + 'substituteEntities' => $dom->substituteEntities, + ]; + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $formatOutput = $dom->formatOutput; + $dom->formatOutput = true; + $a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()]; + $dom->formatOutput = $formatOutput; + } + + return $a; + } + + public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'data' => $dom->data, + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'name' => $dom->name, + 'specified' => $dom->specified, + 'value' => $dom->value, + 'ownerElement' => $dom->ownerElement, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + + return $a; + } + + public static function castElement(\DOMElement $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'tagName' => $dom->tagName, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + + return $a; + } + + public static function castText(\DOMText $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'wholeText' => $dom->wholeText, + ]; + + return $a; + } + + public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'typeName' => $dom->typeName, + 'typeNamespace' => $dom->typeNamespace, + ]; + + return $a; + } + + public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'severity' => $dom->severity, + 'message' => $dom->message, + 'type' => $dom->type, + 'relatedException' => $dom->relatedException, + 'related_data' => $dom->related_data, + 'location' => $dom->location, + ]; + + return $a; + } + + public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'lineNumber' => $dom->lineNumber, + 'columnNumber' => $dom->columnNumber, + 'offset' => $dom->offset, + 'relatedNode' => $dom->relatedNode, + 'uri' => $dom->uri ? new LinkStub($dom->uri, $dom->lineNumber) : $dom->uri, + ]; + + return $a; + } + + public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'name' => $dom->name, + 'entities' => $dom->entities, + 'notations' => $dom->notations, + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'internalSubset' => $dom->internalSubset, + ]; + + return $a; + } + + public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + ]; + + return $a; + } + + public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'notationName' => $dom->notationName, + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'version' => $dom->version, + ]; + + return $a; + } + + public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'target' => $dom->target, + 'data' => $dom->data, + ]; + + return $a; + } + + public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, bool $isNested) + { + $a += [ + 'document' => $dom->document, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DateCaster.php b/vendor/symfony/var-dumper/Caster/DateCaster.php new file mode 100644 index 0000000..18641fb --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DateCaster.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DateTimeInterface related classes to array representation. + * + * @author Dany Maillard + * + * @final + */ +class DateCaster +{ + private const PERIOD_LIMIT = 3; + + public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, bool $isNested, int $filter) + { + $prefix = Caster::PREFIX_VIRTUAL; + $location = $d->getTimezone()->getLocation(); + $fromNow = (new \DateTime())->diff($d); + + $title = $d->format('l, F j, Y') + ."\n".self::formatInterval($fromNow).' from now' + .($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '') + ; + + unset( + $a[Caster::PREFIX_DYNAMIC.'date'], + $a[Caster::PREFIX_DYNAMIC.'timezone'], + $a[Caster::PREFIX_DYNAMIC.'timezone_type'] + ); + $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title); + + $stub->class .= $d->format(' @U'); + + return $a; + } + + public static function castInterval(\DateInterval $interval, array $a, Stub $stub, bool $isNested, int $filter) + { + $now = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); + $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp(); + $title = number_format($numberOfSeconds, 0, '.', ' ').'s'; + + $i = [Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a; + } + + private static function formatInterval(\DateInterval $i): string + { + $format = '%R '; + + if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) { + $d = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); + $i = $d->diff($d->add($i)); // recalculate carry over points + $format .= 0 < $i->days ? '%ad ' : ''; + } else { + $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : ''); + } + + $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : ''; + $format = '%R ' === $format ? '0s' : $format; + + return $i->format(rtrim($format)); + } + + public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, bool $isNested, int $filter) + { + $location = $timeZone->getLocation(); + $formatted = (new \DateTime('now', $timeZone))->format($location ? 'e (P)' : 'P'); + $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : ''; + + $z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a; + } + + public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, bool $isNested, int $filter) + { + $dates = []; + foreach (clone $p as $i => $d) { + if (self::PERIOD_LIMIT === $i) { + $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); + $dates[] = sprintf('%s more', ($end = $p->getEndDate()) + ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) + : $p->recurrences - $i + ); + break; + } + $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d)); + } + + $period = sprintf( + 'every %s, from %s%s %s', + self::formatInterval($p->getDateInterval()), + $p->include_start_date ? '[' : ']', + self::formatDateTime($p->getStartDate()), + ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end).(\PHP_VERSION_ID >= 80200 && $p->include_end_date ? ']' : '[') : 'recurring '.$p->recurrences.' time/s' + ); + + $p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; + } + + private static function formatDateTime(\DateTimeInterface $d, string $extra = ''): string + { + return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra); + } + + private static function formatSeconds(string $s, string $us): string + { + return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); + } +} diff --git a/vendor/symfony/var-dumper/Caster/DoctrineCaster.php b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php new file mode 100644 index 0000000..129b2cb --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DoctrineCaster.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 Symfony\Component\VarDumper\Caster; + +use Doctrine\Common\Proxy\Proxy as CommonProxy; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Proxy\Proxy as OrmProxy; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Doctrine related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class DoctrineCaster +{ + public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, bool $isNested) + { + foreach (['__cloner__', '__initializer__'] as $k) { + if (\array_key_exists($k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, bool $isNested) + { + foreach (['_entityPersister', '_identifier'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, bool $isNested) + { + foreach (['snapshot', 'association', 'typeClass'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) { + $a[$k] = new CutStub($a[$k]); + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsCaster.php b/vendor/symfony/var-dumper/Caster/DsCaster.php new file mode 100644 index 0000000..b34b670 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsCaster.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ds\Collection; +use Ds\Map; +use Ds\Pair; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Ds extension classes to array representation. + * + * @author Jáchym Toušek + * + * @final + */ +class DsCaster +{ + public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'count'] = $c->count(); + $a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity(); + + if (!$c instanceof Map) { + $a += $c->toArray(); + } + + return $a; + } + + public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c as $k => $v) { + $a[] = new DsPairStub($k, $v); + } + + return $a; + } + + public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c->toArray() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $stub->class = Pair::class; + $stub->value = null; + $stub->handle = 0; + + $a = $c->value; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsPairStub.php b/vendor/symfony/var-dumper/Caster/DsPairStub.php new file mode 100644 index 0000000..a1dcc15 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsPairStub.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + */ +class DsPairStub extends Stub +{ + public function __construct($key, $value) + { + $this->value = [ + Caster::PREFIX_VIRTUAL.'key' => $key, + Caster::PREFIX_VIRTUAL.'value' => $value, + ]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/EnumStub.php b/vendor/symfony/var-dumper/Caster/EnumStub.php new file mode 100644 index 0000000..7a4e98a --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/EnumStub.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents an enumeration of values. + * + * @author Nicolas Grekas + */ +class EnumStub extends Stub +{ + public $dumpKeys = true; + + public function __construct(array $values, bool $dumpKeys = true) + { + $this->value = $values; + $this->dumpKeys = $dumpKeys; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ExceptionCaster.php b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php new file mode 100644 index 0000000..7f5cb65 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php @@ -0,0 +1,388 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * Casts common Exception classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class ExceptionCaster +{ + public static $srcContext = 1; + public static $traceArgs = true; + public static $errorTypes = [ + \E_DEPRECATED => 'E_DEPRECATED', + \E_USER_DEPRECATED => 'E_USER_DEPRECATED', + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + \E_ERROR => 'E_ERROR', + \E_WARNING => 'E_WARNING', + \E_PARSE => 'E_PARSE', + \E_NOTICE => 'E_NOTICE', + \E_CORE_ERROR => 'E_CORE_ERROR', + \E_CORE_WARNING => 'E_CORE_WARNING', + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', + \E_USER_ERROR => 'E_USER_ERROR', + \E_USER_WARNING => 'E_USER_WARNING', + \E_USER_NOTICE => 'E_USER_NOTICE', + \E_STRICT => 'E_STRICT', + ]; + + private static $framesCache = []; + + public static function castError(\Error $e, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter); + } + + public static function castException(\Exception $e, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter); + } + + public static function castErrorException(\ErrorException $e, array $a, Stub $stub, bool $isNested) + { + if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + return $a; + } + + public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, bool $isNested) + { + $trace = Caster::PREFIX_VIRTUAL.'trace'; + $prefix = Caster::PREFIX_PROTECTED; + $xPrefix = "\0Exception\0"; + + if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) { + $b = (array) $a[$xPrefix.'previous']; + $class = get_debug_type($a[$xPrefix.'previous']); + self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']); + $a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value)); + } + + unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']); + + return $a; + } + + public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, bool $isNested) + { + $sPrefix = "\0".SilencedErrorContext::class."\0"; + + if (!isset($a[$s = $sPrefix.'severity'])) { + return $a; + } + + if (isset(self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + $trace = [[ + 'file' => $a[$sPrefix.'file'], + 'line' => $a[$sPrefix.'line'], + ]]; + + if (isset($a[$sPrefix.'trace'])) { + $trace = array_merge($trace, $a[$sPrefix.'trace']); + } + + unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']); + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + + return $a; + } + + public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, bool $isNested) + { + if (!$isNested) { + return $a; + } + $stub->class = ''; + $stub->handle = 0; + $frames = $trace->value; + $prefix = Caster::PREFIX_VIRTUAL; + + $a = []; + $j = \count($frames); + if (0 > $i = $trace->sliceOffset) { + $i = max(0, $j + $i); + } + if (!isset($trace->value[$i])) { + return []; + } + $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : ''; + $frames[] = ['function' => '']; + $collapse = false; + + for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) { + $f = $frames[$i]; + $call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'] : '???'; + + $frame = new FrameStub( + [ + 'object' => $f['object'] ?? null, + 'class' => $f['class'] ?? null, + 'type' => $f['type'] ?? null, + 'function' => $f['function'] ?? null, + ] + $frames[$i - 1], + false, + true + ); + $f = self::castFrameStub($frame, [], $frame, true); + if (isset($f[$prefix.'src'])) { + foreach ($f[$prefix.'src']->value as $label => $frame) { + if (str_starts_with($label, "\0~collapse=0")) { + if ($collapse) { + $label = substr_replace($label, '1', 11, 1); + } else { + $collapse = true; + } + } + $label = substr_replace($label, "title=Stack level $j.&", 2, 0); + } + $f = $frames[$i - 1]; + if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) { + $frame->value['arguments'] = new ArgsStub($f['args'], $f['function'] ?? null, $f['class'] ?? null); + } + } elseif ('???' !== $lastCall) { + $label = new ClassStub($lastCall); + if (isset($label->attr['ellipsis'])) { + $label->attr['ellipsis'] += 2; + $label = substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level $j.", 2, 0).$label->value.'()'; + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$label->value.'()'; + } + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall; + } + $a[substr_replace($label, sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $frame; + + $lastCall = $call; + } + if (null !== $trace->sliceLength) { + $a = \array_slice($a, 0, $trace->sliceLength, true); + } + + return $a; + } + + public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, bool $isNested) + { + if (!$isNested) { + return $a; + } + $f = $frame->value; + $prefix = Caster::PREFIX_VIRTUAL; + + if (isset($f['file'], $f['line'])) { + $cacheKey = $f; + unset($cacheKey['object'], $cacheKey['args']); + $cacheKey[] = self::$srcContext; + $cacheKey = implode('-', $cacheKey); + + if (isset(self::$framesCache[$cacheKey])) { + $a[$prefix.'src'] = self::$framesCache[$cacheKey]; + } else { + if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) { + $f['file'] = substr($f['file'], 0, -\strlen($match[0])); + $f['line'] = (int) $match[1]; + } + $src = $f['line']; + $srcKey = $f['file']; + $ellipsis = new LinkStub($srcKey, 0); + $srcAttr = 'collapse='.(int) $ellipsis->inVendor; + $ellipsisTail = $ellipsis->attr['ellipsis-tail'] ?? 0; + $ellipsis = $ellipsis->attr['ellipsis'] ?? 0; + + if (is_file($f['file']) && 0 <= self::$srcContext) { + if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) { + $template = null; + if (isset($f['object'])) { + $template = $f['object']; + } elseif ((new \ReflectionClass($f['class']))->isInstantiable()) { + $template = unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); + } + if (null !== $template) { + $ellipsis = 0; + $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); + $templateInfo = $template->getDebugInfo(); + if (isset($templateInfo[$f['line']])) { + if (!method_exists($template, 'getSourceContext') || !is_file($templatePath = $template->getSourceContext()->getPath())) { + $templatePath = null; + } + if ($templateSrc) { + $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f); + $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']]; + } + } + } + } + if ($srcKey == $f['file']) { + $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f); + $srcKey .= ':'.$f['line']; + if ($ellipsis) { + $ellipsis += 1 + \strlen($f['line']); + } + } + $srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']); + } else { + $srcAttr .= '&separator=:'; + } + $srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis='.$ellipsis.'&ellipsis-tail='.$ellipsisTail : ''; + self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(["\0~$srcAttr\0$srcKey" => $src]); + } + } + + unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']); + if ($frame->inTraceStub) { + unset($a[$prefix.'class'], $a[$prefix.'type'], $a[$prefix.'function']); + } + foreach ($a as $k => $v) { + if (!$v) { + unset($a[$k]); + } + } + if ($frame->keepArgs && !empty($f['args'])) { + $a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']); + } + + return $a; + } + + private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter): array + { + if (isset($a[$xPrefix.'trace'])) { + $trace = $a[$xPrefix.'trace']; + unset($a[$xPrefix.'trace']); // Ensures the trace is always last + } else { + $trace = []; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) { + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + } + if (empty($a[$xPrefix.'previous'])) { + unset($a[$xPrefix.'previous']); + } + unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']); + + if (isset($a[Caster::PREFIX_PROTECTED.'message']) && str_contains($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) { + $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $a[Caster::PREFIX_PROTECTED.'message']); + } + + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + $a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + + return $a; + } + + private static function traceUnshift(array &$trace, ?string $class, string $file, int $line): void + { + if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) { + return; + } + array_unshift($trace, [ + 'function' => $class ? 'new '.$class : null, + 'file' => $file, + 'line' => $line, + ]); + } + + private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame): EnumStub + { + $srcLines = explode("\n", $srcLines); + $src = []; + + for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { + $src[] = ($srcLines[$i] ?? '')."\n"; + } + + if ($frame['function'] ?? false) { + $stub = new CutStub(new \stdClass()); + $stub->class = (isset($frame['class']) ? $frame['class'].$frame['type'] : '').$frame['function']; + $stub->type = Stub::TYPE_OBJECT; + $stub->attr['cut_hash'] = true; + $stub->attr['file'] = $frame['file']; + $stub->attr['line'] = $frame['line']; + + try { + $caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']); + $stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, true, Caster::EXCLUDE_VERBOSE)); + + if ($f = $caller->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $caller->getStartLine(); + } + } catch (\ReflectionException $e) { + // ignore fake class/function + } + + $srcLines = ["\0~separator=\0" => $stub]; + } else { + $stub = null; + $srcLines = []; + } + + $ltrim = 0; + do { + $pad = null; + for ($i = $srcContext << 1; $i >= 0; --$i) { + if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) { + if (null === $pad) { + $pad = $c; + } + if ((' ' !== $c && "\t" !== $c) || $pad !== $c) { + break; + } + } + } + ++$ltrim; + } while (0 > $i && null !== $pad); + + --$ltrim; + + foreach ($src as $i => $c) { + if ($ltrim) { + $c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t"); + } + $c = substr($c, 0, -1); + if ($i !== $srcContext) { + $c = new ConstStub('default', $c); + } else { + $c = new ConstStub($c, $stub ? 'in '.$stub->class : ''); + if (null !== $file) { + $c->attr['file'] = $file; + $c->attr['line'] = $line; + } + } + $c->attr['lang'] = $lang; + $srcLines[sprintf("\0~separator=› &%d\0", $i + $line - $srcContext)] = $c; + } + + return new EnumStub($srcLines); + } +} diff --git a/vendor/symfony/var-dumper/Caster/FiberCaster.php b/vendor/symfony/var-dumper/Caster/FiberCaster.php new file mode 100644 index 0000000..c74a9e5 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/FiberCaster.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Fiber related classes to array representation. + * + * @author Grégoire Pineau + */ +final class FiberCaster +{ + public static function castFiber(\Fiber $fiber, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($fiber->isTerminated()) { + $status = 'terminated'; + } elseif ($fiber->isRunning()) { + $status = 'running'; + } elseif ($fiber->isSuspended()) { + $status = 'suspended'; + } elseif ($fiber->isStarted()) { + $status = 'started'; + } else { + $status = 'not started'; + } + + $a[$prefix.'status'] = $status; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/FrameStub.php b/vendor/symfony/var-dumper/Caster/FrameStub.php new file mode 100644 index 0000000..8786755 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/FrameStub.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 Symfony\Component\VarDumper\Caster; + +/** + * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas + */ +class FrameStub extends EnumStub +{ + public $keepArgs; + public $inTraceStub; + + public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false) + { + $this->value = $frame; + $this->keepArgs = $keepArgs; + $this->inTraceStub = $inTraceStub; + } +} diff --git a/vendor/symfony/var-dumper/Caster/GmpCaster.php b/vendor/symfony/var-dumper/Caster/GmpCaster.php new file mode 100644 index 0000000..b018cc7 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/GmpCaster.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts GMP objects to array representation. + * + * @author Hamza Amrouche + * @author Nicolas Grekas + * + * @final + */ +class GmpCaster +{ + public static function castGmp(\GMP $gmp, array $a, Stub $stub, bool $isNested, int $filter): array + { + $a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp)); + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImagineCaster.php b/vendor/symfony/var-dumper/Caster/ImagineCaster.php new file mode 100644 index 0000000..d1289da --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImagineCaster.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 Symfony\Component\VarDumper\Caster; + +use Imagine\Image\ImageInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class ImagineCaster +{ + public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array + { + $imgData = $c->get('png'); + if (\strlen($imgData) > 1 * 1000 * 1000) { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()), + ]; + } else { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImgStub.php b/vendor/symfony/var-dumper/Caster/ImgStub.php new file mode 100644 index 0000000..a16681f --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImgStub.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * @author Grégoire Pineau + */ +class ImgStub extends ConstStub +{ + public function __construct(string $data, string $contentType, string $size = '') + { + $this->value = ''; + $this->attr['img-data'] = $data; + $this->attr['img-size'] = $size; + $this->attr['content-type'] = $contentType; + } +} diff --git a/vendor/symfony/var-dumper/Caster/IntlCaster.php b/vendor/symfony/var-dumper/Caster/IntlCaster.php new file mode 100644 index 0000000..1ed91d4 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/IntlCaster.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * @author Jan Schädlich + * + * @final + */ +class IntlCaster +{ + public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + return self::castError($c, $a); + } + + public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += 3; + + return self::castError($c, $a); + } + + $a += [ + Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub( + [ + 'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY), + 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED), + 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN), + 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS), + 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS), + 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS), + 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS), + 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS), + 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS), + 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER), + 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE), + 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE), + 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), + 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH), + 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION), + 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE), + 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED), + 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS), + 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS), + 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE), + ] + ), + Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub( + [ + 'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX), + 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX), + 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX), + 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX), + 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER), + 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE), + 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET), + 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS), + ] + ), + Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub( + [ + 'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL), + 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL), + 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL), + 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL), + 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL), + 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL), + 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL), + 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL), + 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL), + 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL), + 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL), + 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL), + 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL), + 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL), + 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL), + 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL), + 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL), + 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL), + ] + ), + ]; + + return self::castError($c, $a); + } + + public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(), + Caster::PREFIX_VIRTUAL.'id' => $c->getID(), + Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(), + ]; + + if ($c->useDaylightTime()) { + $a += [ + Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(), + ]; + } + + return self::castError($c, $a); + } + + public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'type' => $c->getType(), + Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(), + Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(), + Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'time' => $c->getTime(), + Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(), + Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(), + Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(), + Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(), + Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(), + Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + private static function castError(object $c, array $a): array + { + if ($errorCode = $c->getErrorCode()) { + $a += [ + Caster::PREFIX_VIRTUAL.'error_code' => $errorCode, + Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/LinkStub.php b/vendor/symfony/var-dumper/Caster/LinkStub.php new file mode 100644 index 0000000..7e07803 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/LinkStub.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a file or a URL. + * + * @author Nicolas Grekas + */ +class LinkStub extends ConstStub +{ + public $inVendor = false; + + private static $vendorRoots; + private static $composerRoots; + + public function __construct(string $label, int $line = 0, string $href = null) + { + $this->value = $label; + + if (null === $href) { + $href = $label; + } + if (!\is_string($href)) { + return; + } + if (str_starts_with($href, 'file://')) { + if ($href === $label) { + $label = substr($label, 7); + } + $href = substr($href, 7); + } elseif (str_contains($href, '://')) { + $this->attr['href'] = $href; + + return; + } + if (!is_file($href)) { + return; + } + if ($line) { + $this->attr['line'] = $line; + } + if ($label !== $this->attr['file'] = realpath($href) ?: $href) { + return; + } + if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) { + $this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1; + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(implode('', \array_slice(explode(\DIRECTORY_SEPARATOR, substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0); + } elseif (3 < \count($ellipsis = explode(\DIRECTORY_SEPARATOR, $href))) { + $this->attr['ellipsis'] = 2 + \strlen(implode('', \array_slice($ellipsis, -2))); + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1; + } + } + + private function getComposerRoot(string $file, bool &$inVendor) + { + if (null === self::$vendorRoots) { + self::$vendorRoots = []; + + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && str_starts_with($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $v = \dirname($r->getFileName(), 2); + if (is_file($v.'/composer/installed.json')) { + self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR; + } + } + } + } + $inVendor = false; + + if (isset(self::$composerRoots[$dir = \dirname($file)])) { + return self::$composerRoots[$dir]; + } + + foreach (self::$vendorRoots as $root) { + if ($inVendor = str_starts_with($file, $root)) { + return $root; + } + } + + $parent = $dir; + while (!@is_file($parent.'/composer.json')) { + if (!@file_exists($parent)) { + // open_basedir restriction in effect + break; + } + if ($parent === \dirname($parent)) { + return self::$composerRoots[$dir] = false; + } + + $parent = \dirname($parent); + } + + return self::$composerRoots[$dir] = $parent.\DIRECTORY_SEPARATOR; + } +} diff --git a/vendor/symfony/var-dumper/Caster/MemcachedCaster.php b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php new file mode 100644 index 0000000..cfef19a --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/MemcachedCaster.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Jan Schädlich + * + * @final + */ +class MemcachedCaster +{ + private static $optionConstants; + private static $defaultOptions; + + public static function castMemcached(\Memcached $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(), + Caster::PREFIX_VIRTUAL.'options' => new EnumStub( + self::getNonDefaultOptions($c) + ), + ]; + + return $a; + } + + private static function getNonDefaultOptions(\Memcached $c): array + { + self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions(); + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + $nonDefaultOptions = []; + foreach (self::$optionConstants as $constantKey => $value) { + if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) { + $nonDefaultOptions[$constantKey] = $option; + } + } + + return $nonDefaultOptions; + } + + private static function discoverDefaultOptions(): array + { + $defaultMemcached = new \Memcached(); + $defaultMemcached->addServer('127.0.0.1', 11211); + + $defaultOptions = []; + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + foreach (self::$optionConstants as $constantKey => $value) { + $defaultOptions[$constantKey] = $defaultMemcached->getOption($value); + } + + return $defaultOptions; + } + + private static function getOptionConstants(): array + { + $reflectedMemcached = new \ReflectionClass(\Memcached::class); + + $optionConstants = []; + foreach ($reflectedMemcached->getConstants() as $constantKey => $value) { + if (str_starts_with($constantKey, 'OPT_')) { + $optionConstants[$constantKey] = $value; + } + } + + return $optionConstants; + } +} diff --git a/vendor/symfony/var-dumper/Caster/MysqliCaster.php b/vendor/symfony/var-dumper/Caster/MysqliCaster.php new file mode 100644 index 0000000..bfe6f08 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/MysqliCaster.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class MysqliCaster +{ + public static function castMysqliDriver(\mysqli_driver $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($a as $k => $v) { + if (isset($c->$k)) { + $a[$k] = $c->$k; + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PdoCaster.php b/vendor/symfony/var-dumper/Caster/PdoCaster.php new file mode 100644 index 0000000..140473b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PdoCaster.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts PDO related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class PdoCaster +{ + private const PDO_ATTRIBUTES = [ + 'CASE' => [ + \PDO::CASE_LOWER => 'LOWER', + \PDO::CASE_NATURAL => 'NATURAL', + \PDO::CASE_UPPER => 'UPPER', + ], + 'ERRMODE' => [ + \PDO::ERRMODE_SILENT => 'SILENT', + \PDO::ERRMODE_WARNING => 'WARNING', + \PDO::ERRMODE_EXCEPTION => 'EXCEPTION', + ], + 'TIMEOUT', + 'PREFETCH', + 'AUTOCOMMIT', + 'PERSISTENT', + 'DRIVER_NAME', + 'SERVER_INFO', + 'ORACLE_NULLS' => [ + \PDO::NULL_NATURAL => 'NATURAL', + \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING', + \PDO::NULL_TO_STRING => 'TO_STRING', + ], + 'CLIENT_VERSION', + 'SERVER_VERSION', + 'STATEMENT_CLASS', + 'EMULATE_PREPARES', + 'CONNECTION_STATUS', + 'STRINGIFY_FETCHES', + 'DEFAULT_FETCH_MODE' => [ + \PDO::FETCH_ASSOC => 'ASSOC', + \PDO::FETCH_BOTH => 'BOTH', + \PDO::FETCH_LAZY => 'LAZY', + \PDO::FETCH_NUM => 'NUM', + \PDO::FETCH_OBJ => 'OBJ', + ], + ]; + + public static function castPdo(\PDO $c, array $a, Stub $stub, bool $isNested) + { + $attr = []; + $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); + $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + foreach (self::PDO_ATTRIBUTES as $k => $v) { + if (!isset($k[0])) { + $k = $v; + $v = []; + } + + try { + $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k)); + if ($v && isset($v[$attr[$k]])) { + $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]); + } + } catch (\Exception $e) { + } + } + if (isset($attr[$k = 'STATEMENT_CLASS'][1])) { + if ($attr[$k][1]) { + $attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]); + } + $attr[$k][0] = new ClassStub($attr[$k][0]); + } + + $prefix = Caster::PREFIX_VIRTUAL; + $a += [ + $prefix.'inTransaction' => method_exists($c, 'inTransaction'), + $prefix.'errorInfo' => $c->errorInfo(), + $prefix.'attributes' => new EnumStub($attr), + ]; + + if ($a[$prefix.'inTransaction']) { + $a[$prefix.'inTransaction'] = $c->inTransaction(); + } else { + unset($a[$prefix.'inTransaction']); + } + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode); + + return $a; + } + + public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $a[$prefix.'errorInfo'] = $c->errorInfo(); + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PgSqlCaster.php b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php new file mode 100644 index 0000000..d8e5b52 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts pqsql resources to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class PgSqlCaster +{ + private const PARAM_CODES = [ + 'server_encoding', + 'client_encoding', + 'is_superuser', + 'session_authorization', + 'DateStyle', + 'TimeZone', + 'IntervalStyle', + 'integer_datetimes', + 'application_name', + 'standard_conforming_strings', + ]; + + private const TRANSACTION_STATUS = [ + \PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE', + \PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE', + \PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS', + \PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR', + \PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN', + ]; + + private const RESULT_STATUS = [ + \PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY', + \PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK', + \PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK', + \PGSQL_COPY_OUT => 'PGSQL_COPY_OUT', + \PGSQL_COPY_IN => 'PGSQL_COPY_IN', + \PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE', + \PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR', + \PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR', + ]; + + private const DIAG_CODES = [ + 'severity' => \PGSQL_DIAG_SEVERITY, + 'sqlstate' => \PGSQL_DIAG_SQLSTATE, + 'message' => \PGSQL_DIAG_MESSAGE_PRIMARY, + 'detail' => \PGSQL_DIAG_MESSAGE_DETAIL, + 'hint' => \PGSQL_DIAG_MESSAGE_HINT, + 'statement position' => \PGSQL_DIAG_STATEMENT_POSITION, + 'internal position' => \PGSQL_DIAG_INTERNAL_POSITION, + 'internal query' => \PGSQL_DIAG_INTERNAL_QUERY, + 'context' => \PGSQL_DIAG_CONTEXT, + 'file' => \PGSQL_DIAG_SOURCE_FILE, + 'line' => \PGSQL_DIAG_SOURCE_LINE, + 'function' => \PGSQL_DIAG_SOURCE_FUNCTION, + ]; + + public static function castLargeObject($lo, array $a, Stub $stub, bool $isNested) + { + $a['seek position'] = pg_lo_tell($lo); + + return $a; + } + + public static function castLink($link, array $a, Stub $stub, bool $isNested) + { + $a['status'] = pg_connection_status($link); + $a['status'] = new ConstStub(\PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']); + $a['busy'] = pg_connection_busy($link); + + $a['transaction'] = pg_transaction_status($link); + if (isset(self::TRANSACTION_STATUS[$a['transaction']])) { + $a['transaction'] = new ConstStub(self::TRANSACTION_STATUS[$a['transaction']], $a['transaction']); + } + + $a['pid'] = pg_get_pid($link); + $a['last error'] = pg_last_error($link); + $a['last notice'] = pg_last_notice($link); + $a['host'] = pg_host($link); + $a['port'] = pg_port($link); + $a['dbname'] = pg_dbname($link); + $a['options'] = pg_options($link); + $a['version'] = pg_version($link); + + foreach (self::PARAM_CODES as $v) { + if (false !== $s = pg_parameter_status($link, $v)) { + $a['param'][$v] = $s; + } + } + + $a['param']['client_encoding'] = pg_client_encoding($link); + $a['param'] = new EnumStub($a['param']); + + return $a; + } + + public static function castResult($result, array $a, Stub $stub, bool $isNested) + { + $a['num rows'] = pg_num_rows($result); + $a['status'] = pg_result_status($result); + if (isset(self::RESULT_STATUS[$a['status']])) { + $a['status'] = new ConstStub(self::RESULT_STATUS[$a['status']], $a['status']); + } + $a['command-completion tag'] = pg_result_status($result, \PGSQL_STATUS_STRING); + + if (-1 === $a['num rows']) { + foreach (self::DIAG_CODES as $k => $v) { + $a['error'][$k] = pg_result_error_field($result, $v); + } + } + + $a['affected rows'] = pg_affected_rows($result); + $a['last OID'] = pg_last_oid($result); + + $fields = pg_num_fields($result); + + for ($i = 0; $i < $fields; ++$i) { + $field = [ + 'name' => pg_field_name($result, $i), + 'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)), + 'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)), + 'nullable' => (bool) pg_field_is_null($result, $i), + 'storage' => pg_field_size($result, $i).' bytes', + 'display' => pg_field_prtlen($result, $i).' chars', + ]; + if (' (OID: )' === $field['table']) { + $field['table'] = null; + } + if ('-1 bytes' === $field['storage']) { + $field['storage'] = 'variable size'; + } elseif ('1 bytes' === $field['storage']) { + $field['storage'] = '1 byte'; + } + if ('1 chars' === $field['display']) { + $field['display'] = '1 char'; + } + $a['fields'][] = new EnumStub($field); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php new file mode 100644 index 0000000..e712019 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.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 Symfony\Component\VarDumper\Caster; + +use ProxyManager\Proxy\ProxyInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @final + */ +class ProxyManagerCaster +{ + public static function castProxy(ProxyInterface $c, array $a, Stub $stub, bool $isNested) + { + if ($parent = get_parent_class($c)) { + $stub->class .= ' - '.$parent; + } + $stub->class .= '@proxy'; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php b/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php new file mode 100644 index 0000000..db4bba8 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/RdKafkaCaster.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use RdKafka\Conf; +use RdKafka\Exception as RdKafkaException; +use RdKafka\KafkaConsumer; +use RdKafka\Message; +use RdKafka\Metadata\Broker as BrokerMetadata; +use RdKafka\Metadata\Collection as CollectionMetadata; +use RdKafka\Metadata\Partition as PartitionMetadata; +use RdKafka\Metadata\Topic as TopicMetadata; +use RdKafka\Topic; +use RdKafka\TopicConf; +use RdKafka\TopicPartition; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts RdKafka related classes to array representation. + * + * @author Romain Neutron + */ +class RdKafkaCaster +{ + public static function castKafkaConsumer(KafkaConsumer $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + try { + $assignment = $c->getAssignment(); + } catch (RdKafkaException $e) { + $assignment = []; + } + + $a += [ + $prefix.'subscription' => $c->getSubscription(), + $prefix.'assignment' => $assignment, + ]; + + $a += self::extractMetadata($c); + + return $a; + } + + public static function castTopic(Topic $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'name' => $c->getName(), + ]; + + return $a; + } + + public static function castTopicPartition(TopicPartition $c, array $a) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'offset' => $c->getOffset(), + $prefix.'partition' => $c->getPartition(), + $prefix.'topic' => $c->getTopic(), + ]; + + return $a; + } + + public static function castMessage(Message $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'errstr' => $c->errstr(), + ]; + + return $a; + } + + public static function castConf(Conf $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($c->dump() as $key => $value) { + $a[$prefix.$key] = $value; + } + + return $a; + } + + public static function castTopicConf(TopicConf $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($c->dump() as $key => $value) { + $a[$prefix.$key] = $value; + } + + return $a; + } + + public static function castRdKafka(\RdKafka $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'out_q_len' => $c->getOutQLen(), + ]; + + $a += self::extractMetadata($c); + + return $a; + } + + public static function castCollectionMetadata(CollectionMetadata $c, array $a, Stub $stub, bool $isNested) + { + $a += iterator_to_array($c); + + return $a; + } + + public static function castTopicMetadata(TopicMetadata $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'name' => $c->getTopic(), + $prefix.'partitions' => $c->getPartitions(), + ]; + + return $a; + } + + public static function castPartitionMetadata(PartitionMetadata $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'id' => $c->getId(), + $prefix.'err' => $c->getErr(), + $prefix.'leader' => $c->getLeader(), + ]; + + return $a; + } + + public static function castBrokerMetadata(BrokerMetadata $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'id' => $c->getId(), + $prefix.'host' => $c->getHost(), + $prefix.'port' => $c->getPort(), + ]; + + return $a; + } + + private static function extractMetadata($c) + { + $prefix = Caster::PREFIX_VIRTUAL; + + try { + $m = $c->getMetadata(true, null, 500); + } catch (RdKafkaException $e) { + return []; + } + + return [ + $prefix.'orig_broker_id' => $m->getOrigBrokerId(), + $prefix.'orig_broker_name' => $m->getOrigBrokerName(), + $prefix.'brokers' => $m->getBrokers(), + $prefix.'topics' => $m->getTopics(), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/RedisCaster.php b/vendor/symfony/var-dumper/Caster/RedisCaster.php new file mode 100644 index 0000000..8f97eaa --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/RedisCaster.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Redis class from ext-redis to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class RedisCaster +{ + private const SERIALIZERS = [ + \Redis::SERIALIZER_NONE => 'NONE', + \Redis::SERIALIZER_PHP => 'PHP', + 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY + ]; + + private const MODES = [ + \Redis::ATOMIC => 'ATOMIC', + \Redis::MULTI => 'MULTI', + \Redis::PIPELINE => 'PIPELINE', + ]; + + private const COMPRESSION_MODES = [ + 0 => 'NONE', // Redis::COMPRESSION_NONE + 1 => 'LZF', // Redis::COMPRESSION_LZF + ]; + + private const FAILOVER_OPTIONS = [ + \RedisCluster::FAILOVER_NONE => 'NONE', + \RedisCluster::FAILOVER_ERROR => 'ERROR', + \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE', + \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES', + ]; + + public static function castRedis(\Redis $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if (!$connected = $c->isConnected()) { + return $a + [ + $prefix.'isConnected' => $connected, + ]; + } + + $mode = $c->getMode(); + + return $a + [ + $prefix.'isConnected' => $connected, + $prefix.'host' => $c->getHost(), + $prefix.'port' => $c->getPort(), + $prefix.'auth' => $c->getAuth(), + $prefix.'mode' => isset(self::MODES[$mode]) ? new ConstStub(self::MODES[$mode], $mode) : $mode, + $prefix.'dbNum' => $c->getDbNum(), + $prefix.'timeout' => $c->getTimeout(), + $prefix.'lastError' => $c->getLastError(), + $prefix.'persistentId' => $c->getPersistentID(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + return $a + [ + $prefix.'hosts' => $c->_hosts(), + $prefix.'function' => ClassStub::wrapCallable($c->_function()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER); + + $a += [ + $prefix.'_masters' => $c->_masters(), + $prefix.'_redir' => $c->_redir(), + $prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c, [ + 'SLAVE_FAILOVER' => isset(self::FAILOVER_OPTIONS[$failover]) ? new ConstStub(self::FAILOVER_OPTIONS[$failover], $failover) : $failover, + ]), + ]; + + return $a; + } + + /** + * @param \Redis|\RedisArray|\RedisCluster $redis + */ + private static function getRedisOptions($redis, array $options = []): EnumStub + { + $serializer = $redis->getOption(\Redis::OPT_SERIALIZER); + if (\is_array($serializer)) { + foreach ($serializer as &$v) { + if (isset(self::SERIALIZERS[$v])) { + $v = new ConstStub(self::SERIALIZERS[$v], $v); + } + } + } elseif (isset(self::SERIALIZERS[$serializer])) { + $serializer = new ConstStub(self::SERIALIZERS[$serializer], $serializer); + } + + $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0; + if (\is_array($compression)) { + foreach ($compression as &$v) { + if (isset(self::COMPRESSION_MODES[$v])) { + $v = new ConstStub(self::COMPRESSION_MODES[$v], $v); + } + } + } elseif (isset(self::COMPRESSION_MODES[$compression])) { + $compression = new ConstStub(self::COMPRESSION_MODES[$compression], $compression); + } + + $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0; + if (\is_array($retry)) { + foreach ($retry as &$v) { + $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v); + } + } else { + $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry); + } + + $options += [ + 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0, + 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT), + 'COMPRESSION' => $compression, + 'SERIALIZER' => $serializer, + 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX), + 'SCAN' => $retry, + ]; + + return new EnumStub($options); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php new file mode 100644 index 0000000..274ee0d --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php @@ -0,0 +1,442 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Reflector related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class ReflectionCaster +{ + public const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo']; + + private const EXTRA_MAP = [ + 'docComment' => 'getDocComment', + 'extension' => 'getExtensionName', + 'isDisabled' => 'isDisabled', + 'isDeprecated' => 'isDeprecated', + 'isInternal' => 'isInternal', + 'isUserDefined' => 'isUserDefined', + 'isGenerator' => 'isGenerator', + 'isVariadic' => 'isVariadic', + ]; + + public static function castClosure(\Closure $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + $c = new \ReflectionFunction($c); + + $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); + + if (!str_contains($c->name, '{closure}')) { + $stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name; + unset($a[$prefix.'class']); + } + unset($a[$prefix.'extra']); + + $stub->class .= self::getSignature($a); + + if ($f = $c->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $c->getStartLine(); + } + + unset($a[$prefix.'parameters']); + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a); + + return []; + } + + if ($f) { + $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine()); + $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + return $a; + } + + public static function unsetClosureFileInfo(\Closure $c, array $a) + { + unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']); + + return $a; + } + + public static function castGenerator(\Generator $c, array $a, Stub $stub, bool $isNested) + { + // Cannot create ReflectionGenerator based on a terminated Generator + try { + $reflectionGenerator = new \ReflectionGenerator($c); + } catch (\Exception $e) { + $a[Caster::PREFIX_VIRTUAL.'closed'] = true; + + return $a; + } + + return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested); + } + + public static function castType(\ReflectionType $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($c instanceof \ReflectionNamedType || \PHP_VERSION_ID < 80000) { + $a += [ + $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c, + $prefix.'allowsNull' => $c->allowsNull(), + $prefix.'isBuiltin' => $c->isBuiltin(), + ]; + } elseif ($c instanceof \ReflectionUnionType || $c instanceof \ReflectionIntersectionType) { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + self::addMap($a, $c, [ + 'types' => 'getTypes', + ]); + } else { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + } + + return $a; + } + + public static function castAttribute(\ReflectionAttribute $c, array $a, Stub $stub, bool $isNested) + { + self::addMap($a, $c, [ + 'name' => 'getName', + 'arguments' => 'getArguments', + ]); + + return $a; + } + + public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($c->getThis()) { + $a[$prefix.'this'] = new CutStub($c->getThis()); + } + $function = $c->getFunction(); + $frame = [ + 'class' => $function->class ?? null, + 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null, + 'function' => $function->name, + 'file' => $c->getExecutingFile(), + 'line' => $c->getExecutingLine(), + ]; + if ($trace = $c->getTrace(\DEBUG_BACKTRACE_IGNORE_ARGS)) { + $function = new \ReflectionGenerator($c->getExecutingGenerator()); + array_unshift($trace, [ + 'function' => 'yield', + 'file' => $function->getExecutingFile(), + 'line' => $function->getExecutingLine() - (int) (\PHP_VERSION_ID < 80100), + ]); + $trace[] = $frame; + $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1); + } else { + $function = new FrameStub($frame, false, true); + $function = ExceptionCaster::castFrameStub($function, [], $function, true); + $a[$prefix.'executing'] = $function[$prefix.'src']; + } + + $a[Caster::PREFIX_VIRTUAL.'closed'] = false; + + return $a; + } + + public static function castClass(\ReflectionClass $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($n = \Reflection::getModifierNames($c->getModifiers())) { + $a[$prefix.'modifiers'] = implode(' ', $n); + } + + self::addMap($a, $c, [ + 'extends' => 'getParentClass', + 'implements' => 'getInterfaceNames', + 'constants' => 'getReflectionConstants', + ]); + + foreach ($c->getProperties() as $n) { + $a[$prefix.'properties'][$n->name] = $n; + } + + foreach ($c->getMethods() as $n) { + $a[$prefix.'methods'][$n->name] = $n; + } + + self::addAttributes($a, $c, $prefix); + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'returnsReference' => 'returnsReference', + 'returnType' => 'getReturnType', + 'class' => 'getClosureScopeClass', + 'this' => 'getClosureThis', + ]); + + if (isset($a[$prefix.'returnType'])) { + $v = $a[$prefix.'returnType']; + $v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType'] instanceof \ReflectionNamedType && $a[$prefix.'returnType']->allowsNull() && 'mixed' !== $v ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } + if (isset($a[$prefix.'class'])) { + $a[$prefix.'class'] = new ClassStub($a[$prefix.'class']); + } + if (isset($a[$prefix.'this'])) { + $a[$prefix.'this'] = new CutStub($a[$prefix.'this']); + } + + foreach ($c->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isVariadic()) { + $k = '...'.$k; + } + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + $a[$prefix.'parameters'][$k] = $v; + } + if (isset($a[$prefix.'parameters'])) { + $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']); + } + + self::addAttributes($a, $c, $prefix); + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) { + foreach ($v as $k => &$v) { + if (\is_object($v)) { + $a[$prefix.'use']['$'.$k] = new CutStub($v); + } else { + $a[$prefix.'use']['$'.$k] = &$v; + } + } + unset($v); + $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']); + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castClassConstant(\ReflectionClassConstant $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + $a[Caster::PREFIX_VIRTUAL.'value'] = $c->getValue(); + + self::addAttributes($a, $c); + + return $a; + } + + public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + return $a; + } + + public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'position' => 'getPosition', + 'isVariadic' => 'isVariadic', + 'byReference' => 'isPassedByReference', + 'allowsNull' => 'allowsNull', + ]); + + self::addAttributes($a, $c, $prefix); + + if ($v = $c->getType()) { + $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + } + + if (isset($a[$prefix.'typeHint'])) { + $v = $a[$prefix.'typeHint']; + $a[$prefix.'typeHint'] = new ClassStub($v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } else { + unset($a[$prefix.'allowsNull']); + } + + if ($c->isOptional()) { + try { + $a[$prefix.'default'] = $v = $c->getDefaultValue(); + if ($c->isDefaultValueConstant()) { + $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); + } + if (null === $v) { + unset($a[$prefix.'allowsNull']); + } + } catch (\ReflectionException $e) { + } + } + + return $a; + } + + public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + self::addAttributes($a, $c); + self::addExtra($a, $c); + + return $a; + } + + public static function castReference(\ReflectionReference $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId(); + + return $a; + } + + public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, bool $isNested) + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'dependencies' => 'getDependencies', + 'iniEntries' => 'getIniEntries', + 'isPersistent' => 'isPersistent', + 'isTemporary' => 'isTemporary', + 'constants' => 'getConstants', + 'functions' => 'getFunctions', + 'classes' => 'getClasses', + ]); + + return $a; + } + + public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, bool $isNested) + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'author' => 'getAuthor', + 'copyright' => 'getCopyright', + 'url' => 'getURL', + ]); + + return $a; + } + + public static function getSignature(array $a) + { + $prefix = Caster::PREFIX_VIRTUAL; + $signature = ''; + + if (isset($a[$prefix.'parameters'])) { + foreach ($a[$prefix.'parameters']->value as $k => $param) { + $signature .= ', '; + if ($type = $param->getType()) { + if (!$type instanceof \ReflectionNamedType) { + $signature .= $type.' '; + } else { + if (!$param->isOptional() && $param->allowsNull() && 'mixed' !== $type->getName()) { + $signature .= '?'; + } + $signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' '; + } + } + $signature .= $k; + + if (!$param->isDefaultValueAvailable()) { + continue; + } + $v = $param->getDefaultValue(); + $signature .= ' = '; + + if ($param->isDefaultValueConstant()) { + $signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1); + } elseif (null === $v) { + $signature .= 'null'; + } elseif (\is_array($v)) { + $signature .= $v ? '[…'.\count($v).']' : '[]'; + } elseif (\is_string($v)) { + $signature .= 10 > \strlen($v) && !str_contains($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'"; + } elseif (\is_bool($v)) { + $signature .= $v ? 'true' : 'false'; + } elseif (\is_object($v)) { + $signature .= 'new '.substr(strrchr('\\'.get_debug_type($v), '\\'), 1); + } else { + $signature .= $v; + } + } + } + $signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')'; + + if (isset($a[$prefix.'returnType'])) { + $signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1); + } + + return $signature; + } + + private static function addExtra(array &$a, \Reflector $c) + { + $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : []; + + if (method_exists($c, 'getFileName') && $m = $c->getFileName()) { + $x['file'] = new LinkStub($m, $c->getStartLine()); + $x['line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + self::addMap($x, $c, self::EXTRA_MAP, ''); + + if ($x) { + $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x); + } + } + + private static function addMap(array &$a, object $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) + { + foreach ($map as $k => $m) { + if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) { + continue; + } + + if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) { + $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m; + } + } + } + + private static function addAttributes(array &$a, \Reflector $c, string $prefix = Caster::PREFIX_VIRTUAL): void + { + if (\PHP_VERSION_ID >= 80000) { + foreach ($c->getAttributes() as $n) { + $a[$prefix.'attributes'][] = $n; + } + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/ResourceCaster.php b/vendor/symfony/var-dumper/Caster/ResourceCaster.php new file mode 100644 index 0000000..2c34ca9 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ResourceCaster.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts common resource types to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class ResourceCaster +{ + /** + * @param \CurlHandle|resource $h + */ + public static function castCurl($h, array $a, Stub $stub, bool $isNested): array + { + return curl_getinfo($h); + } + + public static function castDba($dba, array $a, Stub $stub, bool $isNested) + { + $list = dba_list(); + $a['file'] = $list[(int) $dba]; + + return $a; + } + + public static function castProcess($process, array $a, Stub $stub, bool $isNested) + { + return proc_get_status($process); + } + + public static function castStream($stream, array $a, Stub $stub, bool $isNested) + { + $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); + if ($a['uri'] ?? false) { + $a['uri'] = new LinkStub($a['uri']); + } + + return $a; + } + + public static function castStreamContext($stream, array $a, Stub $stub, bool $isNested) + { + return @stream_context_get_params($stream) ?: $a; + } + + public static function castGd($gd, array $a, Stub $stub, bool $isNested) + { + $a['size'] = imagesx($gd).'x'.imagesy($gd); + $a['trueColor'] = imageistruecolor($gd); + + return $a; + } + + public static function castMysqlLink($h, array $a, Stub $stub, bool $isNested) + { + $a['host'] = mysql_get_host_info($h); + $a['protocol'] = mysql_get_proto_info($h); + $a['server'] = mysql_get_server_info($h); + + return $a; + } + + public static function castOpensslX509($h, array $a, Stub $stub, bool $isNested) + { + $stub->cut = -1; + $info = openssl_x509_parse($h, false); + + $pin = openssl_pkey_get_public($h); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + $a += [ + 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])), + 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])), + 'expiry' => new ConstStub(date(\DateTime::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), + 'fingerprint' => new EnumStub([ + 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)), + 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)), + 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)), + 'pin-sha256' => new ConstStub($pin), + ]), + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SplCaster.php b/vendor/symfony/var-dumper/Caster/SplCaster.php new file mode 100644 index 0000000..07f4451 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SplCaster.php @@ -0,0 +1,245 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts SPL related classes to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class SplCaster +{ + private const SPL_FILE_OBJECT_FLAGS = [ + \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE', + \SplFileObject::READ_AHEAD => 'READ_AHEAD', + \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY', + \SplFileObject::READ_CSV => 'READ_CSV', + ]; + + public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, bool $isNested) + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, bool $isNested) + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castHeap(\Iterator $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c), + ]; + + return $a; + } + + public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $mode = $c->getIteratorMode(); + $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); + + $a += [ + $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode), + $prefix.'dllist' => iterator_to_array($c), + ]; + $c->setIteratorMode($mode); + + return $a; + } + + public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, bool $isNested) + { + static $map = [ + 'path' => 'getPath', + 'filename' => 'getFilename', + 'basename' => 'getBasename', + 'pathname' => 'getPathname', + 'extension' => 'getExtension', + 'realPath' => 'getRealPath', + 'aTime' => 'getATime', + 'mTime' => 'getMTime', + 'cTime' => 'getCTime', + 'inode' => 'getInode', + 'size' => 'getSize', + 'perms' => 'getPerms', + 'owner' => 'getOwner', + 'group' => 'getGroup', + 'type' => 'getType', + 'writable' => 'isWritable', + 'readable' => 'isReadable', + 'executable' => 'isExecutable', + 'file' => 'isFile', + 'dir' => 'isDir', + 'link' => 'isLink', + 'linkTarget' => 'getLinkTarget', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + unset($a["\0SplFileInfo\0fileName"]); + unset($a["\0SplFileInfo\0pathName"]); + + if (\PHP_VERSION_ID < 80000) { + if (false === $c->getPathname()) { + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } + } else { + try { + $c->isReadable(); + } catch (\RuntimeException $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } catch (\Error $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } + } + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception $e) { + } + } + + if ($a[$prefix.'realPath'] ?? false) { + $a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']); + } + + if (isset($a[$prefix.'perms'])) { + $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']); + } + + static $mapDate = ['aTime', 'mTime', 'cTime']; + foreach ($mapDate as $key) { + if (isset($a[$prefix.$key])) { + $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]); + } + } + + return $a; + } + + public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, bool $isNested) + { + static $map = [ + 'csvControl' => 'getCsvControl', + 'flags' => 'getFlags', + 'maxLineLen' => 'getMaxLineLen', + 'fstat' => 'fstat', + 'eof' => 'eof', + 'key' => 'key', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception $e) { + } + } + + if (isset($a[$prefix.'flags'])) { + $flagsArray = []; + foreach (self::SPL_FILE_OBJECT_FLAGS as $value => $name) { + if ($a[$prefix.'flags'] & $value) { + $flagsArray[] = $name; + } + } + $a[$prefix.'flags'] = new ConstStub(implode('|', $flagsArray), $a[$prefix.'flags']); + } + + if (isset($a[$prefix.'fstat'])) { + $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']); + } + + return $a; + } + + public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, bool $isNested) + { + $storage = []; + unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 + unset($a["\0SplObjectStorage\0storage"]); + + $clone = clone $c; + foreach ($clone as $obj) { + $storage[] = [ + 'object' => $obj, + 'info' => $clone->getInfo(), + ]; + } + + $a += [ + Caster::PREFIX_VIRTUAL.'storage' => $storage, + ]; + + return $a; + } + + public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator(); + + return $a; + } + + public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'object'] = $c->get(); + + return $a; + } + + private static function castSplArray($c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $flags = $c->getFlags(); + + if (!($flags & \ArrayObject::STD_PROP_LIST)) { + $c->setFlags(\ArrayObject::STD_PROP_LIST); + $a = Caster::castObject($c, \get_class($c), method_exists($c, '__debugInfo'), $stub->class); + $c->setFlags($flags); + } + if (\PHP_VERSION_ID < 70400) { + $a[$prefix.'storage'] = $c->getArrayCopy(); + } + $a += [ + $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), + $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), + ]; + if ($c instanceof \ArrayObject) { + $a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass()); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/StubCaster.php b/vendor/symfony/var-dumper/Caster/StubCaster.php new file mode 100644 index 0000000..32ead7c --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/StubCaster.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts a caster's Stub. + * + * @author Nicolas Grekas + * + * @final + */ +class StubCaster +{ + public static function castStub(Stub $c, array $a, Stub $stub, bool $isNested) + { + if ($isNested) { + $stub->type = $c->type; + $stub->class = $c->class; + $stub->value = $c->value; + $stub->handle = $c->handle; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !preg_match('//u', $c->value)) { + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + } + + $a = []; + } + + return $a; + } + + public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, bool $isNested) + { + return $isNested ? $c->preservedSubset : $a; + } + + public static function cutInternals($obj, array $a, Stub $stub, bool $isNested) + { + if ($isNested) { + $stub->cut += \count($a); + + return []; + } + + return $a; + } + + public static function castEnum(EnumStub $c, array $a, Stub $stub, bool $isNested) + { + if ($isNested) { + $stub->class = $c->dumpKeys ? '' : null; + $stub->handle = 0; + $stub->value = null; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + $a = []; + + if ($c->value) { + foreach (array_keys($c->value) as $k) { + $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k; + } + // Preserve references with array_combine() + $a = array_combine($keys, $c->value); + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SymfonyCaster.php b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php new file mode 100644 index 0000000..08428b9 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\Uuid; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @final + */ +class SymfonyCaster +{ + private const REQUEST_GETTERS = [ + 'pathInfo' => 'getPathInfo', + 'requestUri' => 'getRequestUri', + 'baseUrl' => 'getBaseUrl', + 'basePath' => 'getBasePath', + 'method' => 'getMethod', + 'format' => 'getRequestFormat', + ]; + + public static function castRequest(Request $request, array $a, Stub $stub, bool $isNested) + { + $clone = null; + + foreach (self::REQUEST_GETTERS as $prop => $getter) { + $key = Caster::PREFIX_PROTECTED.$prop; + if (\array_key_exists($key, $a) && null === $a[$key]) { + if (null === $clone) { + $clone = clone $request; + } + $a[Caster::PREFIX_VIRTUAL.$prop] = $clone->{$getter}(); + } + } + + return $a; + } + + public static function castHttpClient($client, array $a, Stub $stub, bool $isNested) + { + $multiKey = sprintf("\0%s\0multi", \get_class($client)); + if (isset($a[$multiKey])) { + $a[$multiKey] = new CutStub($a[$multiKey]); + } + + return $a; + } + + public static function castHttpClientResponse($response, array $a, Stub $stub, bool $isNested) + { + $stub->cut += \count($a); + $a = []; + + foreach ($response->getInfo() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castUuid(Uuid $uuid, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $uuid->toBase58(); + $a[Caster::PREFIX_VIRTUAL.'toBase32'] = $uuid->toBase32(); + + // symfony/uid >= 5.3 + if (method_exists($uuid, 'getDateTime')) { + $a[Caster::PREFIX_VIRTUAL.'time'] = $uuid->getDateTime()->format('Y-m-d H:i:s.u \U\T\C'); + } + + return $a; + } + + public static function castUlid(Ulid $ulid, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $ulid->toBase58(); + $a[Caster::PREFIX_VIRTUAL.'toRfc4122'] = $ulid->toRfc4122(); + + // symfony/uid >= 5.3 + if (method_exists($ulid, 'getDateTime')) { + $a[Caster::PREFIX_VIRTUAL.'time'] = $ulid->getDateTime()->format('Y-m-d H:i:s.v \U\T\C'); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/TraceStub.php b/vendor/symfony/var-dumper/Caster/TraceStub.php new file mode 100644 index 0000000..5eea1c8 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/TraceStub.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas + */ +class TraceStub extends Stub +{ + public $keepArgs; + public $sliceOffset; + public $sliceLength; + public $numberingOffset; + + public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, int $sliceLength = null, int $numberingOffset = 0) + { + $this->value = $trace; + $this->keepArgs = $keepArgs; + $this->sliceOffset = $sliceOffset; + $this->sliceLength = $sliceLength; + $this->numberingOffset = $numberingOffset; + } +} diff --git a/vendor/symfony/var-dumper/Caster/UuidCaster.php b/vendor/symfony/var-dumper/Caster/UuidCaster.php new file mode 100644 index 0000000..b102774 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/UuidCaster.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 Symfony\Component\VarDumper\Caster; + +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class UuidCaster +{ + public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'uuid' => (string) $c, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php new file mode 100644 index 0000000..5b45565 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XmlReader class to array representation. + * + * @author Baptiste Clavié + * + * @final + */ +class XmlReaderCaster +{ + private const NODE_TYPES = [ + \XMLReader::NONE => 'NONE', + \XMLReader::ELEMENT => 'ELEMENT', + \XMLReader::ATTRIBUTE => 'ATTRIBUTE', + \XMLReader::TEXT => 'TEXT', + \XMLReader::CDATA => 'CDATA', + \XMLReader::ENTITY_REF => 'ENTITY_REF', + \XMLReader::ENTITY => 'ENTITY', + \XMLReader::PI => 'PI (Processing Instruction)', + \XMLReader::COMMENT => 'COMMENT', + \XMLReader::DOC => 'DOC', + \XMLReader::DOC_TYPE => 'DOC_TYPE', + \XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT', + \XMLReader::NOTATION => 'NOTATION', + \XMLReader::WHITESPACE => 'WHITESPACE', + \XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE', + \XMLReader::END_ELEMENT => 'END_ELEMENT', + \XMLReader::END_ENTITY => 'END_ENTITY', + \XMLReader::XML_DECLARATION => 'XML_DECLARATION', + ]; + + public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, bool $isNested) + { + try { + $properties = [ + 'LOADDTD' => @$reader->getParserProperty(\XMLReader::LOADDTD), + 'DEFAULTATTRS' => @$reader->getParserProperty(\XMLReader::DEFAULTATTRS), + 'VALIDATE' => @$reader->getParserProperty(\XMLReader::VALIDATE), + 'SUBST_ENTITIES' => @$reader->getParserProperty(\XMLReader::SUBST_ENTITIES), + ]; + } catch (\Error $e) { + $properties = [ + 'LOADDTD' => false, + 'DEFAULTATTRS' => false, + 'VALIDATE' => false, + 'SUBST_ENTITIES' => false, + ]; + } + + $props = Caster::PREFIX_VIRTUAL.'parserProperties'; + $info = [ + 'localName' => $reader->localName, + 'prefix' => $reader->prefix, + 'nodeType' => new ConstStub(self::NODE_TYPES[$reader->nodeType], $reader->nodeType), + 'depth' => $reader->depth, + 'isDefault' => $reader->isDefault, + 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement, + 'xmlLang' => $reader->xmlLang, + 'attributeCount' => $reader->attributeCount, + 'value' => $reader->value, + 'namespaceURI' => $reader->namespaceURI, + 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI, + $props => $properties, + ]; + + if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) { + $info[$props] = new EnumStub($info[$props]); + $info[$props]->cut = $count; + } + + $info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count); + // +2 because hasValue and hasAttributes are always filtered + $stub->cut += $count + 2; + + return $a + $info; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php new file mode 100644 index 0000000..ba55fce --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XML resources to array representation. + * + * @author Nicolas Grekas + * + * @final + */ +class XmlResourceCaster +{ + private const XML_ERRORS = [ + \XML_ERROR_NONE => 'XML_ERROR_NONE', + \XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY', + \XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX', + \XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS', + \XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN', + \XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN', + \XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR', + \XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH', + \XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE', + \XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT', + \XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF', + \XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY', + \XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF', + \XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY', + \XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF', + \XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF', + \XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF', + \XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI', + \XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING', + \XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING', + \XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION', + \XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING', + ]; + + public static function castXml($h, array $a, Stub $stub, bool $isNested) + { + $a['current_byte_index'] = xml_get_current_byte_index($h); + $a['current_column_number'] = xml_get_current_column_number($h); + $a['current_line_number'] = xml_get_current_line_number($h); + $a['error_code'] = xml_get_error_code($h); + + if (isset(self::XML_ERRORS[$a['error_code']])) { + $a['error_code'] = new ConstStub(self::XML_ERRORS[$a['error_code']], $a['error_code']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/AbstractCloner.php b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php new file mode 100644 index 0000000..f74a61d --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php @@ -0,0 +1,400 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * AbstractCloner implements a generic caster mechanism for objects and resources. + * + * @author Nicolas Grekas + */ +abstract class AbstractCloner implements ClonerInterface +{ + public static $defaultCasters = [ + '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'], + + 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], + 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], + + 'Fiber' => ['Symfony\Component\VarDumper\Caster\FiberCaster', 'castFiber'], + + 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], + 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], + 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], + 'ReflectionAttribute' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castAttribute'], + 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], + 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'], + 'ReflectionClassConstant' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClassConstant'], + 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'], + 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'], + 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'], + 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'], + 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'], + 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'], + 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'], + + 'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'], + 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'], + 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'], + 'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], + 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], + 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], + 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'], + 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'], + 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], + 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], + 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], + 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], + 'DOMTypeinfo' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'], + 'DOMDomError' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'], + 'DOMLocator' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'], + 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], + 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], + 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], + 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], + 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'], + + 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], + + 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'], + 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'], + 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'], + 'Symfony\Bridge\Monolog\Logger' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\HttpClient\AmpHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\Response\AmpResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'], + 'Symfony\Component\Uid\Ulid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUlid'], + 'Symfony\Component\Uid\Uuid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUuid'], + 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'], + 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'], + 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'], + 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'], + + 'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'], + + 'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'], + + 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'], + 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'], + 'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'], + + 'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'], + 'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'], + 'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'], + 'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'], + 'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'], + + 'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'], + 'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'], + 'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'], + 'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'], + 'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'], + 'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'], + 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'], + 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], + + 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], + 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], + 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], + + 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'], + 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'], + 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'], + 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'], + + 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'], + + 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'], + 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'], + 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'], + 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'], + 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'], + + 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'], + + 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'], + 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'], + 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'], + 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'], + + 'mysqli_driver' => ['Symfony\Component\VarDumper\Caster\MysqliCaster', 'castMysqliDriver'], + + 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + ':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + + ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + + 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + + ':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'], + ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], + ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], + ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], + ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + + 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + + ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], + + 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + + 'RdKafka' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castRdKafka'], + 'RdKafka\Conf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castConf'], + 'RdKafka\KafkaConsumer' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castKafkaConsumer'], + 'RdKafka\Metadata\Broker' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castBrokerMetadata'], + 'RdKafka\Metadata\Collection' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castCollectionMetadata'], + 'RdKafka\Metadata\Partition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castPartitionMetadata'], + 'RdKafka\Metadata\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicMetadata'], + 'RdKafka\Message' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castMessage'], + 'RdKafka\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopic'], + 'RdKafka\TopicPartition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicPartition'], + 'RdKafka\TopicConf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicConf'], + ]; + + protected $maxItems = 2500; + protected $maxString = -1; + protected $minDepth = 1; + + /** + * @var array> + */ + private $casters = []; + + /** + * @var callable|null + */ + private $prevErrorHandler; + + private $classInfo = []; + private $filter = 0; + + /** + * @param callable[]|null $casters A map of casters + * + * @see addCasters + */ + public function __construct(array $casters = null) + { + if (null === $casters) { + $casters = static::$defaultCasters; + } + $this->addCasters($casters); + } + + /** + * Adds casters for resources and objects. + * + * Maps resources or objects types to a callback. + * Types are in the key, with a callable caster for value. + * Resource types are to be prefixed with a `:`, + * see e.g. static::$defaultCasters. + * + * @param callable[] $casters A map of casters + */ + public function addCasters(array $casters) + { + foreach ($casters as $type => $callback) { + $this->casters[$type][] = $callback; + } + } + + /** + * Sets the maximum number of items to clone past the minimum depth in nested structures. + */ + public function setMaxItems(int $maxItems) + { + $this->maxItems = $maxItems; + } + + /** + * Sets the maximum cloned length for strings. + */ + public function setMaxString(int $maxString) + { + $this->maxString = $maxString; + } + + /** + * Sets the minimum tree depth where we are guaranteed to clone all the items. After this + * depth is reached, only setMaxItems items will be cloned. + */ + public function setMinDepth(int $minDepth) + { + $this->minDepth = $minDepth; + } + + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable + * @param int $filter A bit field of Caster::EXCLUDE_* constants + * + * @return Data + */ + public function cloneVar($var, int $filter = 0) + { + $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) { + if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) { + // Cloner never dies + throw new \ErrorException($msg, 0, $type, $file, $line); + } + + if ($this->prevErrorHandler) { + return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); + } + + return false; + }); + $this->filter = $filter; + + if ($gc = gc_enabled()) { + gc_disable(); + } + try { + return new Data($this->doClone($var)); + } finally { + if ($gc) { + gc_enable(); + } + restore_error_handler(); + $this->prevErrorHandler = null; + } + } + + /** + * Effectively clones the PHP variable. + * + * @param mixed $var Any PHP variable + * + * @return array + */ + abstract protected function doClone($var); + + /** + * Casts an object to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array + */ + protected function castObject(Stub $stub, bool $isNested) + { + $obj = $stub->value; + $class = $stub->class; + + if (\PHP_VERSION_ID < 80000 ? "\0" === ($class[15] ?? null) : str_contains($class, "@anonymous\0")) { + $stub->class = get_debug_type($obj); + } + if (isset($this->classInfo[$class])) { + [$i, $parents, $hasDebugInfo, $fileInfo] = $this->classInfo[$class]; + } else { + $i = 2; + $parents = [$class]; + $hasDebugInfo = method_exists($class, '__debugInfo'); + + foreach (class_parents($class) as $p) { + $parents[] = $p; + ++$i; + } + foreach (class_implements($class) as $p) { + $parents[] = $p; + ++$i; + } + $parents[] = '*'; + + $r = new \ReflectionClass($class); + $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [ + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + + $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo]; + } + + $stub->attr += $fileInfo; + $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class); + + try { + while ($i--) { + if (!empty($this->casters[$p = $parents[$i]])) { + foreach ($this->casters[$p] as $callback) { + $a = $callback($obj, $a, $stub, $isNested, $this->filter); + } + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } + + /** + * Casts a resource to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array + */ + protected function castResource(Stub $stub, bool $isNested) + { + $a = []; + $res = $stub->value; + $type = $stub->class; + + try { + if (!empty($this->casters[':'.$type])) { + foreach ($this->casters[':'.$type] as $callback) { + $a = $callback($res, $a, $stub, $isNested, $this->filter); + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/ClonerInterface.php b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php new file mode 100644 index 0000000..90b1495 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/ClonerInterface.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 Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +interface ClonerInterface +{ + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable + * + * @return Data + */ + public function cloneVar($var); +} diff --git a/vendor/symfony/var-dumper/Cloner/Cursor.php b/vendor/symfony/var-dumper/Cloner/Cursor.php new file mode 100644 index 0000000..1fd796d --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Cursor.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 Symfony\Component\VarDumper\Cloner; + +/** + * Represents the current state of a dumper while dumping. + * + * @author Nicolas Grekas + */ +class Cursor +{ + public const HASH_INDEXED = Stub::ARRAY_INDEXED; + public const HASH_ASSOC = Stub::ARRAY_ASSOC; + public const HASH_OBJECT = Stub::TYPE_OBJECT; + public const HASH_RESOURCE = Stub::TYPE_RESOURCE; + + public $depth = 0; + public $refIndex = 0; + public $softRefTo = 0; + public $softRefCount = 0; + public $softRefHandle = 0; + public $hardRefTo = 0; + public $hardRefCount = 0; + public $hardRefHandle = 0; + public $hashType; + public $hashKey; + public $hashKeyIsBinary; + public $hashIndex = 0; + public $hashLength = 0; + public $hashCut = 0; + public $stop = false; + public $attr = []; + public $skipChildren = false; +} diff --git a/vendor/symfony/var-dumper/Cloner/Data.php b/vendor/symfony/var-dumper/Cloner/Data.php new file mode 100644 index 0000000..ea8f0f3 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Data.php @@ -0,0 +1,468 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; + +/** + * @author Nicolas Grekas + */ +class Data implements \ArrayAccess, \Countable, \IteratorAggregate +{ + private $data; + private $position = 0; + private $key = 0; + private $maxDepth = 20; + private $maxItemsPerDepth = -1; + private $useRefHandles = -1; + private $context = []; + + /** + * @param array $data An array as returned by ClonerInterface::cloneVar() + */ + public function __construct(array $data) + { + $this->data = $data; + } + + /** + * @return string|null + */ + public function getType() + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!$item instanceof Stub) { + return \gettype($item); + } + if (Stub::TYPE_STRING === $item->type) { + return 'string'; + } + if (Stub::TYPE_ARRAY === $item->type) { + return 'array'; + } + if (Stub::TYPE_OBJECT === $item->type) { + return $item->class; + } + if (Stub::TYPE_RESOURCE === $item->type) { + return $item->class.' resource'; + } + + return null; + } + + /** + * Returns a native representation of the original value. + * + * @param array|bool $recursive Whether values should be resolved recursively or not + * + * @return string|int|float|bool|array|Data[]|null + */ + public function getValue($recursive = false) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub) { + return $item; + } + if (Stub::TYPE_STRING === $item->type) { + return $item->value; + } + + $children = $item->position ? $this->data[$item->position] : []; + + foreach ($children as $k => $v) { + if ($recursive && !($v = $this->getStub($v)) instanceof Stub) { + continue; + } + $children[$k] = clone $this; + $children[$k]->key = $k; + $children[$k]->position = $item->position; + + if ($recursive) { + if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) { + $recursive = (array) $recursive; + if (isset($recursive[$v->position])) { + continue; + } + $recursive[$v->position] = true; + } + $children[$k] = $children[$k]->getValue($recursive); + } + } + + return $children; + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->getValue()); + } + + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + if (!\is_array($value = $this->getValue())) { + throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, get_debug_type($value))); + } + + yield from $value; + } + + public function __get(string $key) + { + if (null !== $data = $this->seek($key)) { + $item = $this->getStub($data->data[$data->position][$data->key]); + + return $item instanceof Stub || [] === $item ? $data : $item; + } + + return null; + } + + /** + * @return bool + */ + public function __isset(string $key) + { + return null !== $this->seek($key); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->__isset($key); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->__get($key); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + /** + * @return string + */ + public function __toString() + { + $value = $this->getValue(); + + if (!\is_array($value)) { + return (string) $value; + } + + return sprintf('%s (count=%d)', $this->getType(), \count($value)); + } + + /** + * Returns a depth limited clone of $this. + * + * @return static + */ + public function withMaxDepth(int $maxDepth) + { + $data = clone $this; + $data->maxDepth = $maxDepth; + + return $data; + } + + /** + * Limits the number of elements per depth level. + * + * @return static + */ + public function withMaxItemsPerDepth(int $maxItemsPerDepth) + { + $data = clone $this; + $data->maxItemsPerDepth = $maxItemsPerDepth; + + return $data; + } + + /** + * Enables/disables objects' identifiers tracking. + * + * @param bool $useRefHandles False to hide global ref. handles + * + * @return static + */ + public function withRefHandles(bool $useRefHandles) + { + $data = clone $this; + $data->useRefHandles = $useRefHandles ? -1 : 0; + + return $data; + } + + /** + * @return static + */ + public function withContext(array $context) + { + $data = clone $this; + $data->context = $context; + + return $data; + } + + /** + * Seeks to a specific key in nested data structures. + * + * @param string|int $key The key to seek to + * + * @return static|null + */ + public function seek($key) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) { + return null; + } + $keys = [$key]; + + switch ($item->type) { + case Stub::TYPE_OBJECT: + $keys[] = Caster::PREFIX_DYNAMIC.$key; + $keys[] = Caster::PREFIX_PROTECTED.$key; + $keys[] = Caster::PREFIX_VIRTUAL.$key; + $keys[] = "\0$item->class\0$key"; + // no break + case Stub::TYPE_ARRAY: + case Stub::TYPE_RESOURCE: + break; + default: + return null; + } + + $data = null; + $children = $this->data[$item->position]; + + foreach ($keys as $key) { + if (isset($children[$key]) || \array_key_exists($key, $children)) { + $data = clone $this; + $data->key = $key; + $data->position = $item->position; + break; + } + } + + return $data; + } + + /** + * Dumps data with a DumperInterface dumper. + */ + public function dump(DumperInterface $dumper) + { + $refs = [0]; + $cursor = new Cursor(); + + if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) { + $cursor->attr['if_links'] = true; + $cursor->hashType = -1; + $dumper->dumpScalar($cursor, 'default', '^'); + $cursor->attr = ['if_links' => true]; + $dumper->dumpScalar($cursor, 'default', ' '); + $cursor->hashType = 0; + } + + $this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]); + } + + /** + * Depth-first dumping of items. + * + * @param mixed $item A Stub object or the original value being dumped + */ + private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $item) + { + $cursor->refIndex = 0; + $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; + $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0; + $firstSeen = true; + + if (!$item instanceof Stub) { + $cursor->attr = []; + $type = \gettype($item); + if ($item && 'array' === $type) { + $item = $this->getStub($item); + } + } elseif (Stub::TYPE_REF === $item->type) { + if ($item->handle) { + if (!isset($refs[$r = $item->handle - (\PHP_INT_MAX >> 1)])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->hardRefTo = $refs[$r]; + $cursor->hardRefHandle = $this->useRefHandles & $item->handle; + $cursor->hardRefCount = 0 < $item->handle ? $item->refCount : 0; + } + $cursor->attr = $item->attr; + $type = $item->class ?: \gettype($item->value); + $item = $this->getStub($item->value); + } + if ($item instanceof Stub) { + if ($item->refCount) { + if (!isset($refs[$r = $item->handle])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->softRefTo = $refs[$r]; + } + $cursor->softRefHandle = $this->useRefHandles & $item->handle; + $cursor->softRefCount = $item->refCount; + $cursor->attr = $item->attr; + $cut = $item->cut; + + if ($item->position && $firstSeen) { + $children = $this->data[$item->position]; + + if ($cursor->stop) { + if ($cut >= 0) { + $cut += \count($children); + } + $children = []; + } + } else { + $children = []; + } + switch ($item->type) { + case Stub::TYPE_STRING: + $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut); + break; + + case Stub::TYPE_ARRAY: + $item = clone $item; + $item->type = $item->class; + $item->class = $item->value; + // no break + case Stub::TYPE_OBJECT: + case Stub::TYPE_RESOURCE: + $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; + $dumper->enterHash($cursor, $item->type, $item->class, $withChildren); + if ($withChildren) { + if ($cursor->skipChildren) { + $withChildren = false; + $cut = -1; + } else { + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class); + } + } elseif ($children && 0 <= $cut) { + $cut += \count($children); + } + $cursor->skipChildren = false; + $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); + break; + + default: + throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type)); + } + } elseif ('array' === $type) { + $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false); + $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0); + } elseif ('string' === $type) { + $dumper->dumpString($cursor, $item, false, 0); + } else { + $dumper->dumpScalar($cursor, $type, $item); + } + } + + /** + * Dumps children of hash structures. + * + * @return int The final number of removed items + */ + private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys): int + { + $cursor = clone $parentCursor; + ++$cursor->depth; + $cursor->hashType = $hashType; + $cursor->hashIndex = 0; + $cursor->hashLength = \count($children); + $cursor->hashCut = $hashCut; + foreach ($children as $key => $child) { + $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key); + $cursor->hashKey = $dumpKeys ? $key : null; + $this->dumpItem($dumper, $cursor, $refs, $child); + if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { + $parentCursor->stop = true; + + return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut; + } + } + + return $hashCut; + } + + private function getStub($item) + { + if (!$item || !\is_array($item)) { + return $item; + } + + $stub = new Stub(); + $stub->type = Stub::TYPE_ARRAY; + foreach ($item as $stub->class => $stub->position) { + } + if (isset($item[0])) { + $stub->cut = $item[0]; + } + $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0); + + return $stub; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/DumperInterface.php b/vendor/symfony/var-dumper/Cloner/DumperInterface.php new file mode 100644 index 0000000..6d60b72 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/DumperInterface.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\VarDumper\Cloner; + +/** + * DumperInterface used by Data objects. + * + * @author Nicolas Grekas + */ +interface DumperInterface +{ + /** + * Dumps a scalar value. + * + * @param string $type The PHP type of the value being dumped + * @param string|int|float|bool $value The scalar value being dumped + */ + public function dumpScalar(Cursor $cursor, string $type, $value); + + /** + * Dumps a string. + * + * @param string $str The string being dumped + * @param bool $bin Whether $str is UTF-8 or binary encoded + * @param int $cut The number of characters $str has been cut by + */ + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut); + + /** + * Dumps while entering an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + */ + public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild); + + /** + * Dumps while leaving an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut); +} diff --git a/vendor/symfony/var-dumper/Cloner/Stub.php b/vendor/symfony/var-dumper/Cloner/Stub.php new file mode 100644 index 0000000..073c56e --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Stub.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the main properties of a PHP variable. + * + * @author Nicolas Grekas + */ +class Stub +{ + public const TYPE_REF = 1; + public const TYPE_STRING = 2; + public const TYPE_ARRAY = 3; + public const TYPE_OBJECT = 4; + public const TYPE_RESOURCE = 5; + + public const STRING_BINARY = 1; + public const STRING_UTF8 = 2; + + public const ARRAY_ASSOC = 1; + public const ARRAY_INDEXED = 2; + + public $type = self::TYPE_REF; + public $class = ''; + public $value; + public $cut = 0; + public $handle = 0; + public $refCount = 0; + public $position = 0; + public $attr = []; + + private static $defaultProperties = []; + + /** + * @internal + */ + public function __sleep(): array + { + $properties = []; + + if (!isset(self::$defaultProperties[$c = static::class])) { + self::$defaultProperties[$c] = get_class_vars($c); + + foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) { + unset(self::$defaultProperties[$c][$k]); + } + } + + foreach (self::$defaultProperties[$c] as $k => $v) { + if ($this->$k !== $v) { + $properties[] = $k; + } + } + + return $properties; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/VarCloner.php b/vendor/symfony/var-dumper/Cloner/VarCloner.php new file mode 100644 index 0000000..80c4a2f --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/VarCloner.php @@ -0,0 +1,311 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +class VarCloner extends AbstractCloner +{ + private static $gid; + private static $arrayCache = []; + + /** + * {@inheritdoc} + */ + protected function doClone($var) + { + $len = 1; // Length of $queue + $pos = 0; // Number of cloned items past the minimum depth + $refsCounter = 0; // Hard references counter + $queue = [[$var]]; // This breadth-first queue is the return value + $hardRefs = []; // Map of original zval ids to stub objects + $objRefs = []; // Map of original object handles to their stub object counterpart + $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning + $resRefs = []; // Map of original resource handles to their stub object counterpart + $values = []; // Map of stub objects' ids to original values + $maxItems = $this->maxItems; + $maxString = $this->maxString; + $minDepth = $this->minDepth; + $currentDepth = 0; // Current tree depth + $currentDepthFinalIndex = 0; // Final $queue index for current tree depth + $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached + $cookie = (object) []; // Unique object used to detect hard references + $a = null; // Array cast for nested structures + $stub = null; // Stub capturing the main properties of an original item value + // or null if the original value is used directly + + if (!$gid = self::$gid) { + $gid = self::$gid = md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable + } + $arrayStub = new Stub(); + $arrayStub->type = Stub::TYPE_ARRAY; + $fromObjCast = false; + + for ($i = 0; $i < $len; ++$i) { + // Detect when we move on to the next tree depth + if ($i > $currentDepthFinalIndex) { + ++$currentDepth; + $currentDepthFinalIndex = $len - 1; + if ($currentDepth >= $minDepth) { + $minimumDepthReached = true; + } + } + + $refs = $vals = $queue[$i]; + foreach ($vals as $k => $v) { + // $v is the original value or a stub object in case of hard references + + if (\PHP_VERSION_ID >= 70400) { + $zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null; + } else { + $refs[$k] = $cookie; + $zvalRef = $vals[$k] === $cookie; + } + + if ($zvalRef) { + $vals[$k] = &$stub; // Break hard references to make $queue completely + unset($stub); // independent from the original structure + if (\PHP_VERSION_ID >= 70400 ? null !== $vals[$k] = $hardRefs[$zvalRef] ?? null : $v instanceof Stub && isset($hardRefs[spl_object_id($v)])) { + if (\PHP_VERSION_ID >= 70400) { + $v = $vals[$k]; + } else { + $refs[$k] = $vals[$k] = $v; + } + if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { + ++$v->value->refCount; + } + ++$v->refCount; + continue; + } + $vals[$k] = new Stub(); + $vals[$k]->value = $v; + $vals[$k]->handle = ++$refsCounter; + + if (\PHP_VERSION_ID >= 70400) { + $hardRefs[$zvalRef] = $vals[$k]; + } else { + $refs[$k] = $vals[$k]; + $h = spl_object_id($refs[$k]); + $hardRefs[$h] = &$refs[$k]; + $values[$h] = $v; + } + } + // Create $stub when the original value $v cannot be used directly + // If $v is a nested structure, put that structure in array $a + switch (true) { + case null === $v: + case \is_bool($v): + case \is_int($v): + case \is_float($v): + continue 2; + case \is_string($v): + if ('' === $v) { + continue 2; + } + if (!preg_match('//u', $v)) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { + $stub->cut = $cut; + $stub->value = substr($v, 0, -$cut); + } else { + $stub->value = $v; + } + } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_UTF8; + $stub->cut = $cut; + $stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); + } else { + continue 2; + } + $a = null; + break; + + case \is_array($v): + if (!$v) { + continue 2; + } + $stub = $arrayStub; + + if (\PHP_VERSION_ID >= 80100) { + $stub->class = array_is_list($v) ? Stub::ARRAY_INDEXED : Stub::ARRAY_ASSOC; + $a = $v; + break; + } + + $stub->class = Stub::ARRAY_INDEXED; + + $j = -1; + foreach ($v as $gk => $gv) { + if ($gk !== ++$j) { + $stub->class = Stub::ARRAY_ASSOC; + $a = $v; + $a[$gid] = true; + break; + } + } + + // Copies of $GLOBALS have very strange behavior, + // let's detect them with some black magic + if (isset($v[$gid])) { + unset($v[$gid]); + $a = []; + foreach ($v as $gk => &$gv) { + if ($v === $gv && (\PHP_VERSION_ID < 70400 || !isset($hardRefs[\ReflectionReference::fromArrayElement($v, $gk)->getId()]))) { + unset($v); + $v = new Stub(); + $v->value = [$v->cut = \count($gv), Stub::TYPE_ARRAY => 0]; + $v->handle = -1; + if (\PHP_VERSION_ID >= 70400) { + $gv = &$a[$gk]; + $hardRefs[\ReflectionReference::fromArrayElement($a, $gk)->getId()] = &$gv; + } else { + $gv = &$hardRefs[spl_object_id($v)]; + } + $gv = $v; + } + + $a[$gk] = &$gv; + } + unset($gv); + } else { + $a = $v; + } + break; + + case \is_object($v): + if (empty($objRefs[$h = spl_object_id($v)])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_OBJECT; + $stub->class = \get_class($v); + $stub->value = $v; + $stub->handle = $h; + $a = $this->castObject($stub, 0 < $i); + if ($v !== $stub->value) { + if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { + break; + } + $stub->handle = $h = spl_object_id($stub->value); + } + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($objRefs[$h])) { + $objRefs[$h] = $stub; + $objects[] = $v; + } else { + $stub = $objRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + + default: // resource + if (empty($resRefs[$h = (int) $v])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_RESOURCE; + if ('Unknown' === $stub->class = @get_resource_type($v)) { + $stub->class = 'Closed'; + } + $stub->value = $v; + $stub->handle = $h; + $a = $this->castResource($stub, 0 < $i); + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($resRefs[$h])) { + $resRefs[$h] = $stub; + } else { + $stub = $resRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + } + + if ($a) { + if (!$minimumDepthReached || 0 > $maxItems) { + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($pos < $maxItems) { + if ($maxItems < $pos += \count($a)) { + $a = \array_slice($a, 0, $maxItems - $pos, true); + if ($stub->cut >= 0) { + $stub->cut += $pos - $maxItems; + } + } + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($stub->cut >= 0) { + $stub->cut += \count($a); + $stub->position = 0; + } + } + + if ($arrayStub === $stub) { + if ($arrayStub->cut) { + $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; + $arrayStub->cut = 0; + } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { + $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; + } else { + self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; + } + } + + if (!$zvalRef) { + $vals[$k] = $stub; + } elseif (\PHP_VERSION_ID >= 70400) { + $hardRefs[$zvalRef]->value = $stub; + } else { + $refs[$k]->value = $stub; + } + } + + if ($fromObjCast) { + $fromObjCast = false; + $refs = $vals; + $vals = []; + $j = -1; + foreach ($queue[$i] as $k => $v) { + foreach ([$k => true] as $gk => $gv) { + } + if ($gk !== $k) { + $vals = (object) $vals; + $vals->{$k} = $refs[++$j]; + $vals = (array) $vals; + } else { + $vals[$k] = $refs[++$j]; + } + } + } + + $queue[$i] = $vals; + } + + foreach ($values as $h => $v) { + $hardRefs[$h] = $v; + } + + return $queue; + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php new file mode 100644 index 0000000..2afaa7b --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.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\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * Describe collected data clones for cli output. + * + * @author Maxime Steinhausser + * + * @final + */ +class CliDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $lastIdentifier; + + public function __construct(CliDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); + $this->dumper->setColors($output->isDecorated()); + + $rows = [['date', date('r', (int) $context['timestamp'])]]; + $lastIdentifier = $this->lastIdentifier; + $this->lastIdentifier = $clientId; + + $section = "Received from client #$clientId"; + if (isset($context['request'])) { + $request = $context['request']; + $this->lastIdentifier = $request['identifier']; + $section = sprintf('%s %s', $request['method'], $request['uri']); + if ($controller = $request['controller']) { + $rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")]; + } + } elseif (isset($context['cli'])) { + $this->lastIdentifier = $context['cli']['identifier']; + $section = '$ '.$context['cli']['command_line']; + } + + if ($this->lastIdentifier !== $lastIdentifier) { + $io->section($section); + } + + if (isset($context['source'])) { + $source = $context['source']; + $sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']); + if ($fileLink = $source['file_link'] ?? null) { + $sourceInfo = sprintf('%s', $fileLink, $sourceInfo); + } + $rows[] = ['source', $sourceInfo]; + $file = $source['file_relative'] ?? $source['file']; + $rows[] = ['file', $file]; + } + + $io->table([], $rows); + + $this->dumper->dump($data); + $io->newLine(); + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php new file mode 100644 index 0000000..267d27b --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.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 Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Maxime Steinhausser + */ +interface DumpDescriptorInterface +{ + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void; +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php new file mode 100644 index 0000000..636b618 --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.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 Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * Describe collected data clones for html output. + * + * @author Maxime Steinhausser + * + * @final + */ +class HtmlDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $initialized = false; + + public function __construct(HtmlDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + if (!$this->initialized) { + $styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css'); + $scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js'); + $output->writeln(""); + $this->initialized = true; + } + + $title = '-'; + if (isset($context['request'])) { + $request = $context['request']; + $controller = "{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])}"; + $title = sprintf('%s %s', $request['method'], $uri = $request['uri'], $uri); + $dedupIdentifier = $request['identifier']; + } elseif (isset($context['cli'])) { + $title = '$ '.$context['cli']['command_line']; + $dedupIdentifier = $context['cli']['identifier']; + } else { + $dedupIdentifier = uniqid('', true); + } + + $sourceDescription = ''; + if (isset($context['source'])) { + $source = $context['source']; + $projectDir = $source['project_dir'] ?? null; + $sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']); + if (isset($source['file_link'])) { + $sourceDescription = sprintf('%s', $source['file_link'], $sourceDescription); + } + } + + $isoDate = $this->extractDate($context, 'c'); + $tags = array_filter([ + 'controller' => $controller ?? null, + 'project dir' => $projectDir ?? null, + ]); + + $output->writeln(<< +
+
+

$title

+ +
+ {$this->renderTags($tags)} +
+
+

+ $sourceDescription +

+ {$this->dumper->dump($data, true)} +
+ +HTML + ); + } + + private function extractDate(array $context, string $format = 'r'): string + { + return date($format, (int) $context['timestamp']); + } + + private function renderTags(array $tags): string + { + if (!$tags) { + return ''; + } + + $renderedTags = ''; + foreach ($tags as $key => $value) { + $renderedTags .= sprintf('
  • %s%s
  • ', $key, $value); + } + + return << +
      + $renderedTags +
    +
    +HTML; + } +} diff --git a/vendor/symfony/var-dumper/Command/ServerDumpCommand.php b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php new file mode 100644 index 0000000..3a69595 --- /dev/null +++ b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor; +use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface; +use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Server\DumpServer; + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + * + * @final + */ +class ServerDumpCommand extends Command +{ + protected static $defaultName = 'server:dump'; + protected static $defaultDescription = 'Start a dump server that collects and displays dumps in a single place'; + + private $server; + + /** @var DumpDescriptorInterface[] */ + private $descriptors; + + public function __construct(DumpServer $server, array $descriptors = []) + { + $this->server = $server; + $this->descriptors = $descriptors + [ + 'cli' => new CliDescriptor(new CliDumper()), + 'html' => new HtmlDescriptor(new HtmlDumper()), + ]; + + parent::__construct(); + } + + protected function configure() + { + $this + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', implode(', ', $this->getAvailableFormats())), 'cli') + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +%command.name% starts a dump server that collects and displays +dumps in a single place for debugging you application: + + php %command.full_name% + +You can consult dumped data in HTML format in your browser by providing the --format=html option +and redirecting the output to a file: + + php %command.full_name% --format="html" > dump.html + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $format = $input->getOption('format'); + + if (!$descriptor = $this->descriptors[$format] ?? null) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format)); + } + + $errorIo = $io->getErrorStyle(); + $errorIo->title('Symfony Var Dumper Server'); + + $this->server->start(); + + $errorIo->success(sprintf('Server listening on %s', $this->server->getHost())); + $errorIo->comment('Quit the server with CONTROL-C.'); + + $this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) { + $descriptor->describe($io, $data, $context, $clientId); + }); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormats()); + } + } + + private function getAvailableFormats(): array + { + return array_keys($this->descriptors); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/AbstractDumper.php b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php new file mode 100644 index 0000000..ae19faf --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/AbstractDumper.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 Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\DumperInterface; + +/** + * Abstract mechanism for dumping a Data object. + * + * @author Nicolas Grekas + */ +abstract class AbstractDumper implements DataDumperInterface, DumperInterface +{ + public const DUMP_LIGHT_ARRAY = 1; + public const DUMP_STRING_LENGTH = 2; + public const DUMP_COMMA_SEPARATOR = 4; + public const DUMP_TRAILING_COMMA = 8; + + public static $defaultOutput = 'php://output'; + + protected $line = ''; + protected $lineDumper; + protected $outputStream; + protected $decimalPoint; // This is locale dependent + protected $indentPad = ' '; + protected $flags; + + private $charset = ''; + + /** + * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput + * @param string|null $charset The default character encoding to use for non-UTF8 strings + * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + $this->flags = $flags; + $this->setCharset($charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'); + $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point']; + $this->setOutput($output ?: static::$defaultOutput); + if (!$output && \is_string(static::$defaultOutput)) { + static::$defaultOutput = $this->outputStream; + } + } + + /** + * Sets the output destination of the dumps. + * + * @param callable|resource|string $output A line dumper callable, an opened stream or an output path + * + * @return callable|resource|string The previous output destination + */ + public function setOutput($output) + { + $prev = $this->outputStream ?? $this->lineDumper; + + if (\is_callable($output)) { + $this->outputStream = null; + $this->lineDumper = $output; + } else { + if (\is_string($output)) { + $output = fopen($output, 'w'); + } + $this->outputStream = $output; + $this->lineDumper = [$this, 'echoLine']; + } + + return $prev; + } + + /** + * Sets the default character encoding to use for non-UTF8 strings. + * + * @return string The previous charset + */ + public function setCharset(string $charset) + { + $prev = $this->charset; + + $charset = strtoupper($charset); + $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset; + + $this->charset = $charset; + + return $prev; + } + + /** + * Sets the indentation pad string. + * + * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level + * + * @return string The previous indent pad + */ + public function setIndentPad(string $pad) + { + $prev = $this->indentPad; + $this->indentPad = $pad; + + return $prev; + } + + /** + * Dumps a Data object. + * + * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump + * + * @return string|null The dump as string when $output is true + */ + public function dump(Data $data, $output = null) + { + $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point']; + + if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) { + setlocale(\LC_NUMERIC, 'C'); + } + + if ($returnDump = true === $output) { + $output = fopen('php://memory', 'r+'); + } + if ($output) { + $prevOutput = $this->setOutput($output); + } + try { + $data->dump($this); + $this->dumpLine(-1); + + if ($returnDump) { + $result = stream_get_contents($output, -1, 0); + fclose($output); + + return $result; + } + } finally { + if ($output) { + $this->setOutput($prevOutput); + } + if ($locale) { + setlocale(\LC_NUMERIC, $locale); + } + } + + return null; + } + + /** + * Dumps the current line. + * + * @param int $depth The recursive depth in the dumped structure for the line being dumped, + * or -1 to signal the end-of-dump to the line dumper callable + */ + protected function dumpLine(int $depth) + { + ($this->lineDumper)($this->line, $depth, $this->indentPad); + $this->line = ''; + } + + /** + * Generic line dumper callback. + */ + protected function echoLine(string $line, int $depth, string $indentPad) + { + if (-1 !== $depth) { + fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n"); + } + } + + /** + * Converts a non-UTF-8 string to UTF-8. + * + * @return string|null + */ + protected function utf8Encode(?string $s) + { + if (null === $s || preg_match('//u', $s)) { + return $s; + } + + if (!\function_exists('iconv')) { + throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) { + return $c; + } + if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) { + return $c; + } + + return iconv('CP850', 'UTF-8', $s); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/CliDumper.php b/vendor/symfony/var-dumper/Dumper/CliDumper.php new file mode 100644 index 0000000..94dc8ee --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/CliDumper.php @@ -0,0 +1,652 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * CliDumper dumps variables for command line output. + * + * @author Nicolas Grekas + */ +class CliDumper extends AbstractDumper +{ + public static $defaultColors; + public static $defaultOutput = 'php://stdout'; + + protected $colors; + protected $maxStringWidth = 0; + protected $styles = [ + // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + 'default' => '0;38;5;208', + 'num' => '1;38;5;38', + 'const' => '1;38;5;208', + 'str' => '1;38;5;113', + 'note' => '38;5;38', + 'ref' => '38;5;247', + 'public' => '', + 'protected' => '', + 'private' => '', + 'meta' => '38;5;170', + 'key' => '38;5;113', + 'index' => '38;5;38', + ]; + + protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/'; + protected static $controlCharsMap = [ + "\t" => '\t', + "\n" => '\n', + "\v" => '\v', + "\f" => '\f', + "\r" => '\r', + "\033" => '\e', + ]; + + protected $collapseNextHash = false; + protected $expandNextHash = false; + + private $displayOptions = [ + 'fileLinkFormat' => null, + ]; + + private $handlesHrefGracefully; + + /** + * {@inheritdoc} + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + parent::__construct($output, $charset, $flags); + + if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { + // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI + $this->setStyles([ + 'default' => '31', + 'num' => '1;34', + 'const' => '1;31', + 'str' => '1;32', + 'note' => '34', + 'ref' => '1;30', + 'meta' => '35', + 'key' => '32', + 'index' => '34', + ]); + } + + $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; + } + + /** + * Enables/disables colored output. + */ + public function setColors(bool $colors) + { + $this->colors = $colors; + } + + /** + * Sets the maximum number of characters per line for dumped strings. + */ + public function setMaxStringWidth(int $maxStringWidth) + { + $this->maxStringWidth = $maxStringWidth; + } + + /** + * Configures styles. + * + * @param array $styles A map of style names to style definitions + */ + public function setStyles(array $styles) + { + $this->styles = $styles + $this->styles; + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * {@inheritdoc} + */ + public function dumpScalar(Cursor $cursor, string $type, $value) + { + $this->dumpKey($cursor); + + $style = 'const'; + $attr = $cursor->attr; + + switch ($type) { + case 'default': + $style = 'default'; + break; + + case 'integer': + $style = 'num'; + + if (isset($this->styles['integer'])) { + $style = 'integer'; + } + + break; + + case 'double': + $style = 'num'; + + if (isset($this->styles['float'])) { + $style = 'float'; + } + + switch (true) { + case \INF === $value: $value = 'INF'; break; + case -\INF === $value: $value = '-INF'; break; + case is_nan($value): $value = 'NAN'; break; + default: + $value = (string) $value; + if (!str_contains($value, $this->decimalPoint)) { + $value .= $this->decimalPoint.'0'; + } + break; + } + break; + + case 'NULL': + $value = 'null'; + break; + + case 'boolean': + $value = $value ? 'true' : 'false'; + break; + + default: + $attr += ['value' => $this->utf8Encode($value)]; + $value = $this->utf8Encode($type); + break; + } + + $this->line .= $this->style($style, $value, $attr); + + $this->endValue($cursor); + } + + /** + * {@inheritdoc} + */ + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) + { + $this->dumpKey($cursor); + $attr = $cursor->attr; + + if ($bin) { + $str = $this->utf8Encode($str); + } + if ('' === $str) { + $this->line .= '""'; + $this->endValue($cursor); + } else { + $attr += [ + 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0, + 'binary' => $bin, + ]; + $str = $bin && false !== strpos($str, "\0") ? [$str] : explode("\n", $str); + if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) { + unset($str[1]); + $str[0] .= "\n"; + } + $m = \count($str) - 1; + $i = $lineCut = 0; + + if (self::DUMP_STRING_LENGTH & $this->flags) { + $this->line .= '('.$attr['length'].') '; + } + if ($bin) { + $this->line .= 'b'; + } + + if ($m) { + $this->line .= '"""'; + $this->dumpLine($cursor->depth); + } else { + $this->line .= '"'; + } + + foreach ($str as $str) { + if ($i < $m) { + $str .= "\n"; + } + if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) { + $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8'); + $lineCut = $len - $this->maxStringWidth; + } + if ($m && 0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + if ('' !== $str) { + $this->line .= $this->style('str', $str, $attr); + } + if ($i++ == $m) { + if ($m) { + if ('' !== $str) { + $this->dumpLine($cursor->depth); + if (0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + } + $this->line .= '"""'; + } else { + $this->line .= '"'; + } + if ($cut < 0) { + $this->line .= '…'; + $lineCut = 0; + } elseif ($cut) { + $lineCut += $cut; + } + } + if ($lineCut) { + $this->line .= '…'.$lineCut; + $lineCut = 0; + } + + if ($i > $m) { + $this->endValue($cursor); + } else { + $this->dumpLine($cursor->depth); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild) + { + if (null === $this->colors) { + $this->colors = $this->supportsColors(); + } + + $this->dumpKey($cursor); + $attr = $cursor->attr; + + if ($this->collapseNextHash) { + $cursor->skipChildren = true; + $this->collapseNextHash = $hasChild = false; + } + + $class = $this->utf8Encode($class); + if (Cursor::HASH_OBJECT === $type) { + $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).(empty($attr['cut_hash']) ? ' {' : '') : '{'; + } elseif (Cursor::HASH_RESOURCE === $type) { + $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' '); + } else { + $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '['; + } + + if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) { + $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]); + } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { + $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]); + } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) { + $prefix = substr($prefix, 0, -1); + } + + $this->line .= $prefix; + + if ($hasChild) { + $this->dumpLine($cursor->depth); + } + } + + /** + * {@inheritdoc} + */ + public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut) + { + if (empty($cursor->attr['cut_hash'])) { + $this->dumpEllipsis($cursor, $hasChild, $cut); + $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); + } + + $this->endValue($cursor); + } + + /** + * Dumps an ellipsis for cut children. + * + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + protected function dumpEllipsis(Cursor $cursor, bool $hasChild, int $cut) + { + if ($cut) { + $this->line .= ' …'; + if (0 < $cut) { + $this->line .= $cut; + } + if ($hasChild) { + $this->dumpLine($cursor->depth + 1); + } + } + } + + /** + * Dumps a key in a hash structure. + */ + protected function dumpKey(Cursor $cursor) + { + if (null !== $key = $cursor->hashKey) { + if ($cursor->hashKeyIsBinary) { + $key = $this->utf8Encode($key); + } + $attr = ['binary' => $cursor->hashKeyIsBinary]; + $bin = $cursor->hashKeyIsBinary ? 'b' : ''; + $style = 'key'; + switch ($cursor->hashType) { + default: + case Cursor::HASH_INDEXED: + if (self::DUMP_LIGHT_ARRAY & $this->flags) { + break; + } + $style = 'index'; + // no break + case Cursor::HASH_ASSOC: + if (\is_int($key)) { + $this->line .= $this->style($style, $key).' => '; + } else { + $this->line .= $bin.'"'.$this->style($style, $key).'" => '; + } + break; + + case Cursor::HASH_RESOURCE: + $key = "\0~\0".$key; + // no break + case Cursor::HASH_OBJECT: + if (!isset($key[0]) || "\0" !== $key[0]) { + $this->line .= '+'.$bin.$this->style('public', $key).': '; + } elseif (0 < strpos($key, "\0", 1)) { + $key = explode("\0", substr($key, 1), 2); + + switch ($key[0][0]) { + case '+': // User inserted keys + $attr['dynamic'] = true; + $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": '; + break 2; + case '~': + $style = 'meta'; + if (isset($key[0][1])) { + parse_str(substr($key[0], 1), $attr); + $attr += ['binary' => $cursor->hashKeyIsBinary]; + } + break; + case '*': + $style = 'protected'; + $bin = '#'.$bin; + break; + default: + $attr['class'] = $key[0]; + $style = 'private'; + $bin = '-'.$bin; + break; + } + + if (isset($attr['collapse'])) { + if ($attr['collapse']) { + $this->collapseNextHash = true; + } else { + $this->expandNextHash = true; + } + } + + $this->line .= $bin.$this->style($style, $key[1], $attr).($attr['separator'] ?? ': '); + } else { + // This case should not happen + $this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": '; + } + break; + } + + if ($cursor->hardRefTo) { + $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]).' '; + } + } + } + + /** + * Decorates a value with some style. + * + * @param string $style The type of style being applied + * @param string $value The value being styled + * @param array $attr Optional context information + * + * @return string + */ + protected function style(string $style, string $value, array $attr = []) + { + if (null === $this->colors) { + $this->colors = $this->supportsColors(); + } + + if (null === $this->handlesHrefGracefully) { + $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); + } + + if (isset($attr['ellipsis'], $attr['ellipsis-type'])) { + $prefix = substr($value, 0, -$attr['ellipsis']); + if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && str_starts_with($prefix, $_SERVER[$pwd])) { + $prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd])); + } + if (!empty($attr['ellipsis-tail'])) { + $prefix .= substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']); + $value = substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']); + } else { + $value = substr($value, -$attr['ellipsis']); + } + + $value = $this->style('default', $prefix).$this->style($style, $value); + + goto href; + } + + $map = static::$controlCharsMap; + $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : ''; + $endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : ''; + $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) { + $s = $startCchr; + $c = $c[$i = 0]; + do { + $s .= $map[$c[$i]] ?? sprintf('\x%02X', \ord($c[$i])); + } while (isset($c[++$i])); + + return $s.$endCchr; + }, $value, -1, $cchrCount); + + if ($this->colors) { + if ($cchrCount && "\033" === $value[0]) { + $value = substr($value, \strlen($startCchr)); + } else { + $value = "\033[{$this->styles[$style]}m".$value; + } + if ($cchrCount && str_ends_with($value, $endCchr)) { + $value = substr($value, 0, -\strlen($endCchr)); + } else { + $value .= "\033[{$this->styles['default']}m"; + } + } + + href: + if ($this->colors && $this->handlesHrefGracefully) { + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { + if ('note' === $style) { + $value .= "\033]8;;{$href}\033\\^\033]8;;\033\\"; + } else { + $attr['href'] = $href; + } + } + if (isset($attr['href'])) { + $value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\"; + } + } elseif ($attr['if_links'] ?? false) { + return ''; + } + + return $value; + } + + /** + * @return bool + */ + protected function supportsColors() + { + if ($this->outputStream !== static::$defaultOutput) { + return $this->hasColorSupport($this->outputStream); + } + if (null !== static::$defaultColors) { + return static::$defaultColors; + } + if (isset($_SERVER['argv'][1])) { + $colors = $_SERVER['argv']; + $i = \count($colors); + while (--$i > 0) { + if (isset($colors[$i][5])) { + switch ($colors[$i]) { + case '--ansi': + case '--color': + case '--color=yes': + case '--color=force': + case '--color=always': + case '--colors=always': + return static::$defaultColors = true; + + case '--no-ansi': + case '--color=no': + case '--color=none': + case '--color=never': + case '--colors=never': + return static::$defaultColors = false; + } + } + } + } + + $h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null]; + $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'w') : $this->outputStream; + + return static::$defaultColors = $this->hasColorSupport($h); + } + + /** + * {@inheritdoc} + */ + protected function dumpLine(int $depth, bool $endOfValue = false) + { + if ($this->colors) { + $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line); + } + parent::dumpLine($depth); + } + + protected function endValue(Cursor $cursor) + { + if (-1 === $cursor->hashType) { + return; + } + + if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) { + if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) { + $this->line .= ','; + } elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) { + $this->line .= ','; + } + } + + $this->dumpLine($cursor->depth, true); + } + + /** + * Returns true if the stream supports colorization. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @param mixed $stream A CLI output stream + */ + private function hasColorSupport($stream): bool + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + return false; + } + + // Follow https://no-color.org/ + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return stream_isatty($stream); + } + + /** + * Returns true if the Windows terminal supports true color. + * + * Note that this does not check an output stream, but relies on environment + * variables from known implementations, or a PHP and Windows version that + * supports true color. + */ + private function isWindowsTrueColor(): bool + { + $result = 183 <= getenv('ANSICON_VER') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM'); + + if (!$result) { + $version = sprintf( + '%s.%s.%s', + PHP_WINDOWS_VERSION_MAJOR, + PHP_WINDOWS_VERSION_MINOR, + PHP_WINDOWS_VERSION_BUILD + ); + $result = $version >= '10.0.15063'; + } + + return $result; + } + + private function getSourceLink(string $file, int $line) + { + if ($fmt = $this->displayOptions['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line); + } + + return false; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php new file mode 100644 index 0000000..38f8789 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.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 Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Tries to provide context on CLI. + * + * @author Maxime Steinhausser + */ +final class CliContextProvider implements ContextProviderInterface +{ + public function getContext(): ?array + { + if ('cli' !== \PHP_SAPI) { + return null; + } + + return [ + 'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []), + 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php new file mode 100644 index 0000000..532aa0f --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.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 Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Interface to provide contextual data about dump data clones sent to a server. + * + * @author Maxime Steinhausser + */ +interface ContextProviderInterface +{ + public function getContext(): ?array; +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php new file mode 100644 index 0000000..3684a47 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.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\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * Tries to provide context from a request. + * + * @author Maxime Steinhausser + */ +final class RequestContextProvider implements ContextProviderInterface +{ + private $requestStack; + private $cloner; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(0); + $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + } + + public function getContext(): ?array + { + if (null === $request = $this->requestStack->getCurrentRequest()) { + return null; + } + + $controller = $request->attributes->get('_controller'); + + return [ + 'uri' => $request->getUri(), + 'method' => $request->getMethod(), + 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller, + 'identifier' => spl_object_hash($request), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php new file mode 100644 index 0000000..2e2c818 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\VarDumper; +use Twig\Template; + +/** + * Tries to provide context from sources (class name, file, line, code excerpt, ...). + * + * @author Nicolas Grekas + * @author Maxime Steinhausser + */ +final class SourceContextProvider implements ContextProviderInterface +{ + private $limit; + private $charset; + private $projectDir; + private $fileLinkFormatter; + + public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) + { + $this->charset = $charset; + $this->projectDir = $projectDir; + $this->fileLinkFormatter = $fileLinkFormatter; + $this->limit = $limit; + } + + public function getContext(): ?array + { + $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit); + + $file = $trace[1]['file']; + $line = $trace[1]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 2; $i < $this->limit; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && VarDumper::class === $trace[$i]['class'] + ) { + $file = $trace[$i]['file'] ?? $file; + $line = $trace[$i]['line'] ?? $line; + + while (++$i < $this->limit) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = []; + + for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).''; + } + + $fileExcerpt = '
      '.implode("\n", $fileExcerpt).'
    '; + } + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + } + + $context = ['name' => $name, 'file' => $file, 'line' => $line]; + $context['file_excerpt'] = $fileExcerpt; + + if (null !== $this->projectDir) { + $context['project_dir'] = $this->projectDir; + if (str_starts_with($file, $this->projectDir)) { + $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + } + } + + if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) { + $context['file_link'] = $fileLink; + } + + return $context; + } + + private function htmlEncode(string $s): string + { + $html = ''; + + $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($s)); + + return substr(strip_tags($html), 1, -1); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php new file mode 100644 index 0000000..7638417 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.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 Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * @author Kévin Thérage + */ +class ContextualizedDumper implements DataDumperInterface +{ + private $wrappedDumper; + private $contextProviders; + + /** + * @param ContextProviderInterface[] $contextProviders + */ + public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders) + { + $this->wrappedDumper = $wrappedDumper; + $this->contextProviders = $contextProviders; + } + + public function dump(Data $data) + { + $context = []; + foreach ($this->contextProviders as $contextProvider) { + $context[\get_class($contextProvider)] = $contextProvider->getContext(); + } + + $this->wrappedDumper->dump($data->withContext($context)); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php new file mode 100644 index 0000000..b173bcc --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.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 Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * DataDumperInterface for dumping Data objects. + * + * @author Nicolas Grekas + */ +interface DataDumperInterface +{ + public function dump(Data $data); +} diff --git a/vendor/symfony/var-dumper/Dumper/HtmlDumper.php b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php new file mode 100644 index 0000000..af4de96 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php @@ -0,0 +1,986 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * HtmlDumper dumps variables as HTML. + * + * @author Nicolas Grekas + */ +class HtmlDumper extends CliDumper +{ + public static $defaultOutput = 'php://output'; + + protected static $themes = [ + 'dark' => [ + 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#56DB3A', + 'note' => 'color:#1299DA', + 'ref' => 'color:#A0A0A0', + 'public' => 'color:#FFFFFF', + 'protected' => 'color:#FFFFFF', + 'private' => 'color:#FFFFFF', + 'meta' => 'color:#B729D9', + 'key' => 'color:#56DB3A', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#FF8400', + 'ns' => 'user-select:none;', + ], + 'light' => [ + 'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#629755;', + 'note' => 'color:#6897BB', + 'ref' => 'color:#6E6E6E', + 'public' => 'color:#262626', + 'protected' => 'color:#262626', + 'private' => 'color:#262626', + 'meta' => 'color:#B729D9', + 'key' => 'color:#789339', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#CC7832', + 'ns' => 'user-select:none;', + ], + ]; + + protected $dumpHeader; + protected $dumpPrefix = '
    ';
    +    protected $dumpSuffix = '
    '; + protected $dumpId = 'sf-dump'; + protected $colors = true; + protected $headerIsDumped = false; + protected $lastDepth = -1; + protected $styles; + + private $displayOptions = [ + 'maxDepth' => 1, + 'maxStringLength' => 160, + 'fileLinkFormat' => null, + ]; + private $extraDisplayOptions = []; + + /** + * {@inheritdoc} + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + AbstractDumper::__construct($output, $charset, $flags); + $this->dumpId = 'sf-dump-'.mt_rand(); + $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->styles = static::$themes['dark'] ?? self::$themes['dark']; + } + + /** + * {@inheritdoc} + */ + public function setStyles(array $styles) + { + $this->headerIsDumped = false; + $this->styles = $styles + $this->styles; + } + + public function setTheme(string $themeName) + { + if (!isset(static::$themes[$themeName])) { + throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class)); + } + + $this->setStyles(static::$themes[$themeName]); + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->headerIsDumped = false; + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * Sets an HTML header that will be dumped once in the output stream. + */ + public function setDumpHeader(?string $header) + { + $this->dumpHeader = $header; + } + + /** + * Sets an HTML prefix and suffix that will encapse every single dump. + */ + public function setDumpBoundaries(string $prefix, string $suffix) + { + $this->dumpPrefix = $prefix; + $this->dumpSuffix = $suffix; + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data, $output = null, array $extraDisplayOptions = []) + { + $this->extraDisplayOptions = $extraDisplayOptions; + $result = parent::dump($data, $output); + $this->dumpId = 'sf-dump-'.mt_rand(); + + return $result; + } + + /** + * Dumps the HTML header. + */ + protected function getDumpHeader() + { + $this->headerIsDumped = $this->outputStream ?? $this->lineDumper; + + if (null !== $this->dumpHeader) { + return $this->dumpHeader; + } + + $line = str_replace('{$options}', json_encode($this->displayOptions, \JSON_FORCE_OBJECT), <<<'EOHTML' +'.$this->dumpHeader; + } + + /** + * {@inheritdoc} + */ + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) + { + if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { + $this->dumpKey($cursor); + $this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []); + $this->line .= $cursor->depth >= $this->displayOptions['maxDepth'] ? ' ' : ' '; + $this->endValue($cursor); + $this->line .= $this->indentPad; + $this->line .= sprintf('', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data'])); + $this->endValue($cursor); + } else { + parent::dumpString($cursor, $str, $bin, $cut); + } + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild) + { + if (Cursor::HASH_OBJECT === $type) { + $cursor->attr['depth'] = $cursor->depth; + } + parent::enterHash($cursor, $type, $class, false); + + if ($cursor->skipChildren || $cursor->depth >= $this->displayOptions['maxDepth']) { + $cursor->skipChildren = false; + $eol = ' class=sf-dump-compact>'; + } else { + $this->expandNextHash = false; + $eol = ' class=sf-dump-expanded>'; + } + + if ($hasChild) { + $this->line .= 'dumpId, $r); + } + $this->line .= $eol; + $this->dumpLine($cursor->depth); + } + } + + /** + * {@inheritdoc} + */ + public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut) + { + $this->dumpEllipsis($cursor, $hasChild, $cut); + if ($hasChild) { + $this->line .= ''; + } + parent::leaveHash($cursor, $type, $class, $hasChild, 0); + } + + /** + * {@inheritdoc} + */ + protected function style(string $style, string $value, array $attr = []) + { + if ('' === $value) { + return ''; + } + + $v = esc($value); + + if ('ref' === $style) { + if (empty($attr['count'])) { + return sprintf('%s', $v); + } + $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1); + + return sprintf('%s', $this->dumpId, $r, 1 + $attr['count'], $v); + } + + if ('const' === $style && isset($attr['value'])) { + $style .= sprintf(' title="%s"', esc(\is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); + } elseif ('public' === $style) { + $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); + } elseif ('str' === $style && 1 < $attr['length']) { + $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); + } elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && false !== $c = strrpos($value, '\\')) { + $style .= ' title=""'; + $attr += [ + 'ellipsis' => \strlen($value) - $c, + 'ellipsis-type' => 'note', + 'ellipsis-tail' => 1, + ]; + } elseif ('protected' === $style) { + $style .= ' title="Protected property"'; + } elseif ('meta' === $style && isset($attr['title'])) { + $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title']))); + } elseif ('private' === $style) { + $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); + } + $map = static::$controlCharsMap; + + if (isset($attr['ellipsis'])) { + $class = 'sf-dump-ellipsis'; + if (isset($attr['ellipsis-type'])) { + $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']); + } + $label = esc(substr($value, -$attr['ellipsis'])); + $style = str_replace(' title="', " title=\"$v\n", $style); + $v = sprintf('%s', $class, substr($v, 0, -\strlen($label))); + + if (!empty($attr['ellipsis-tail'])) { + $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); + $v .= sprintf('%s%s', $class, substr($label, 0, $tail), substr($label, $tail)); + } else { + $v .= $label; + } + } + + $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { + $s = $b = ''; + }, $v).''; + + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { + $attr['href'] = $href; + } + if (isset($attr['href'])) { + $target = isset($attr['file']) ? '' : ' target="_blank"'; + $v = sprintf('%s', esc($this->utf8Encode($attr['href'])), $target, $v); + } + if (isset($attr['lang'])) { + $v = sprintf('%s', esc($attr['lang']), $v); + } + + return $v; + } + + /** + * {@inheritdoc} + */ + protected function dumpLine(int $depth, bool $endOfValue = false) + { + if (-1 === $this->lastDepth) { + $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; + } + if ($this->headerIsDumped !== ($this->outputStream ?? $this->lineDumper)) { + $this->line = $this->getDumpHeader().$this->line; + } + + if (-1 === $depth) { + $args = ['"'.$this->dumpId.'"']; + if ($this->extraDisplayOptions) { + $args[] = json_encode($this->extraDisplayOptions, \JSON_FORCE_OBJECT); + } + // Replace is for BC + $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args)); + } + $this->lastDepth = $depth; + + $this->line = mb_encode_numericentity($this->line, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'); + + if (-1 === $depth) { + AbstractDumper::dumpLine(0); + } + AbstractDumper::dumpLine($depth); + } + + private function getSourceLink(string $file, int $line) + { + $options = $this->extraDisplayOptions + $this->displayOptions; + + if ($fmt = $options['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); + } + + return false; + } +} + +function esc(string $str) +{ + return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); +} diff --git a/vendor/symfony/var-dumper/Dumper/ServerDumper.php b/vendor/symfony/var-dumper/Dumper/ServerDumper.php new file mode 100644 index 0000000..94795bf --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ServerDumper.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\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; +use Symfony\Component\VarDumper\Server\Connection; + +/** + * ServerDumper forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class ServerDumper implements DataDumperInterface +{ + private $connection; + private $wrappedDumper; + + /** + * @param string $host The server host + * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = []) + { + $this->connection = new Connection($host, $contextProviders); + $this->wrappedDumper = $wrappedDumper; + } + + public function getContextProviders(): array + { + return $this->connection->getContextProviders(); + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data) + { + if (!$this->connection->write($data) && $this->wrappedDumper) { + $this->wrappedDumper->dump($data); + } + } +} diff --git a/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php new file mode 100644 index 0000000..122f0d3 --- /dev/null +++ b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Exception; + +/** + * @author Nicolas Grekas + */ +class ThrowingCasterException extends \Exception +{ + /** + * @param \Throwable $prev The exception thrown from the caster + */ + public function __construct(\Throwable $prev) + { + parent::__construct('Unexpected '.\get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev); + } +} diff --git a/vendor/symfony/var-dumper/LICENSE b/vendor/symfony/var-dumper/LICENSE new file mode 100644 index 0000000..a843ec1 --- /dev/null +++ b/vendor/symfony/var-dumper/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014-2022 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/var-dumper/README.md b/vendor/symfony/var-dumper/README.md new file mode 100644 index 0000000..a0da8c9 --- /dev/null +++ b/vendor/symfony/var-dumper/README.md @@ -0,0 +1,15 @@ +VarDumper Component +=================== + +The VarDumper component provides mechanisms for walking through any arbitrary +PHP variable. It provides a better `dump()` function that you can use instead +of `var_dump()`. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/var-dumper/Resources/bin/var-dump-server b/vendor/symfony/var-dumper/Resources/bin/var-dump-server new file mode 100755 index 0000000..f398fce --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/bin/var-dump-server @@ -0,0 +1,67 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ('cli' !== PHP_SAPI) { + throw new Exception('This script must be run from the command line.'); +} + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + */ + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\VarDumper\Command\ServerDumpCommand; +use Symfony\Component\VarDumper\Server\DumpServer; + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__ . '/../../../../autoload.php') && + !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && + !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') +) { + fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); + exit(1); +} + +if (!class_exists(Application::class)) { + fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL); + exit(1); +} + +$input = new ArgvInput(); +$output = new ConsoleOutput(); +$defaultHost = '127.0.0.1:9912'; +$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true); +$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null; + +$app = new Application(); + +$app->getDefinition()->addOption( + new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost) +); + +$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger))) + ->getApplication() + ->setDefaultCommand($command->getName(), true) + ->run($input, $output) +; diff --git a/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css new file mode 100644 index 0000000..8f706d6 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css @@ -0,0 +1,130 @@ +body { + display: flex; + flex-direction: column-reverse; + justify-content: flex-end; + max-width: 1140px; + margin: auto; + padding: 15px; + word-wrap: break-word; + background-color: #F9F9F9; + color: #222; + font-family: Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.4; +} +p { + margin: 0; +} +a { + color: #218BC3; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +.text-small { + font-size: 12px !important; +} +article { + margin: 5px; + margin-bottom: 10px; +} +article > header > .row { + display: flex; + flex-direction: row; + align-items: baseline; + margin-bottom: 10px; +} +article > header > .row > .col { + flex: 1; + display: flex; + align-items: baseline; +} +article > header > .row > h2 { + font-size: 14px; + color: #222; + font-weight: normal; + font-family: "Lucida Console", monospace, sans-serif; + word-break: break-all; + margin: 20px 5px 0 0; + user-select: all; +} +article > header > .row > h2 > code { + white-space: nowrap; + user-select: none; + color: #cc2255; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 3px; + margin-right: 5px; + padding: 0 3px; +} +article > header > .row > time.col { + flex: 0; + text-align: right; + white-space: nowrap; + color: #999; + font-style: italic; +} +article > header ul.tags { + list-style: none; + padding: 0; + margin: 0; + font-size: 12px; +} +article > header ul.tags > li { + user-select: all; + margin-bottom: 2px; +} +article > header ul.tags > li > span.badge { + display: inline-block; + padding: .25em .4em; + margin-right: 5px; + border-radius: 4px; + background-color: #6c757d3b; + color: #524d4d; + font-size: 12px; + text-align: center; + font-weight: 700; + line-height: 1; + white-space: nowrap; + vertical-align: baseline; + user-select: none; +} +article > section.body { + border: 1px solid #d8d8d8; + background: #FFF; + padding: 10px; + border-radius: 3px; +} +pre.sf-dump { + border-radius: 3px; + margin-bottom: 0; +} +.hidden { + display: none !important; +} +.dumped-tag > .sf-dump { + display: inline-block; + margin: 0; + padding: 1px 5px; + line-height: 1.4; + vertical-align: top; + background-color: transparent; + user-select: auto; +} +.dumped-tag > pre.sf-dump, +.dumped-tag > .sf-dump-default { + color: #CC7832; + background: none; +} +.dumped-tag > .sf-dump .sf-dump-str { color: #629755; } +.dumped-tag > .sf-dump .sf-dump-private, +.dumped-tag > .sf-dump .sf-dump-protected, +.dumped-tag > .sf-dump .sf-dump-public { color: #262626; } +.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; } +.dumped-tag > .sf-dump .sf-dump-key { color: #789339; } +.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; } +.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } +.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; } +.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; } diff --git a/vendor/symfony/var-dumper/Resources/functions/dump.php b/vendor/symfony/var-dumper/Resources/functions/dump.php new file mode 100644 index 0000000..f26aad5 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/functions/dump.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\VarDumper\VarDumper; + +if (!function_exists('dump')) { + /** + * @author Nicolas Grekas + */ + function dump($var, ...$moreVars) + { + VarDumper::dump($var); + + foreach ($moreVars as $v) { + VarDumper::dump($v); + } + + if (1 < func_num_args()) { + return func_get_args(); + } + + return $var; + } +} + +if (!function_exists('dd')) { + /** + * @return never + */ + function dd(...$vars) + { + if (!in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + + foreach ($vars as $v) { + VarDumper::dump($v); + } + + exit(1); + } +} diff --git a/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js new file mode 100644 index 0000000..63101e5 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js @@ -0,0 +1,10 @@ +document.addEventListener('DOMContentLoaded', function() { + let prev = null; + Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) { + const dedupId = article.dataset.dedupId; + if (dedupId === prev) { + article.getElementsByTagName('header')[0].classList.add('hidden'); + } + prev = dedupId; + }); +}); diff --git a/vendor/symfony/var-dumper/Server/Connection.php b/vendor/symfony/var-dumper/Server/Connection.php new file mode 100644 index 0000000..d0611a1 --- /dev/null +++ b/vendor/symfony/var-dumper/Server/Connection.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * Forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class Connection +{ + private $host; + private $contextProviders; + + /** + * @var resource|null + */ + private $socket; + + /** + * @param string $host The server host + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, array $contextProviders = []) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->contextProviders = $contextProviders; + } + + public function getContextProviders(): array + { + return $this->contextProviders; + } + + public function write(Data $data): bool + { + $socketIsFresh = !$this->socket; + if (!$this->socket = $this->socket ?: $this->createSocket()) { + return false; + } + + $context = ['timestamp' => microtime(true)]; + foreach ($this->contextProviders as $name => $provider) { + $context[$name] = $provider->getContext(); + } + $context = array_filter($context); + $encodedPayload = base64_encode(serialize([$data, $context]))."\n"; + + set_error_handler([self::class, 'nullErrorHandler']); + try { + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + if (!$socketIsFresh) { + stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR); + fclose($this->socket); + $this->socket = $this->createSocket(); + } + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + } finally { + restore_error_handler(); + } + + return false; + } + + private static function nullErrorHandler(int $t, string $m) + { + // no-op + } + + private function createSocket() + { + set_error_handler([self::class, 'nullErrorHandler']); + try { + return stream_socket_client($this->host, $errno, $errstr, 3, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT); + } finally { + restore_error_handler(); + } + } +} diff --git a/vendor/symfony/var-dumper/Server/DumpServer.php b/vendor/symfony/var-dumper/Server/DumpServer.php new file mode 100644 index 0000000..f9735db --- /dev/null +++ b/vendor/symfony/var-dumper/Server/DumpServer.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Psr\Log\LoggerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * A server collecting Data clones sent by a ServerDumper. + * + * @author Maxime Steinhausser + * + * @final + */ +class DumpServer +{ + private $host; + private $logger; + + /** + * @var resource|null + */ + private $socket; + + public function __construct(string $host, LoggerInterface $logger = null) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->logger = $logger; + } + + public function start(): void + { + if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { + throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno); + } + } + + public function listen(callable $callback): void + { + if (null === $this->socket) { + $this->start(); + } + + foreach ($this->getMessages() as $clientId => $message) { + if ($this->logger) { + $this->logger->info('Received a payload from client {clientId}', ['clientId' => $clientId]); + } + + $payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]); + + // Impossible to decode the message, give up. + if (false === $payload) { + if ($this->logger) { + $this->logger->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]); + } + + continue; + } + + if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) { + if ($this->logger) { + $this->logger->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]); + } + + continue; + } + + [$data, $context] = $payload; + + $callback($data, $context, $clientId); + } + } + + public function getHost(): string + { + return $this->host; + } + + private function getMessages(): iterable + { + $sockets = [(int) $this->socket => $this->socket]; + $write = []; + + while (true) { + $read = $sockets; + stream_select($read, $write, $write, null); + + foreach ($read as $stream) { + if ($this->socket === $stream) { + $stream = stream_socket_accept($this->socket); + $sockets[(int) $stream] = $stream; + } elseif (feof($stream)) { + unset($sockets[(int) $stream]); + fclose($stream); + } else { + yield (int) $stream => fgets($stream); + } + } + } + } +} diff --git a/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php new file mode 100644 index 0000000..33d60c0 --- /dev/null +++ b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Test; + +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Nicolas Grekas + */ +trait VarDumperTestTrait +{ + /** + * @internal + */ + private $varDumperConfig = [ + 'casters' => [], + 'flags' => null, + ]; + + protected function setUpVarDumper(array $casters, int $flags = null): void + { + $this->varDumperConfig['casters'] = $casters; + $this->varDumperConfig['flags'] = $flags; + } + + /** + * @after + */ + protected function tearDownVarDumper(): void + { + $this->varDumperConfig['casters'] = []; + $this->varDumperConfig['flags'] = null; + } + + public function assertDumpEquals($expected, $data, int $filter = 0, string $message = '') + { + $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + public function assertDumpMatchesFormat($expected, $data, int $filter = 0, string $message = '') + { + $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + protected function getDump($data, $key = null, int $filter = 0): ?string + { + if (null === $flags = $this->varDumperConfig['flags']) { + $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; + $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0; + $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0; + } + + $cloner = new VarCloner(); + $cloner->addCasters($this->varDumperConfig['casters']); + $cloner->setMaxItems(-1); + $dumper = new CliDumper(null, null, $flags); + $dumper->setColors(false); + $data = $cloner->cloneVar($data, $filter)->withRefHandles(false); + if (null !== $key && null === $data = $data->seek($key)) { + return null; + } + + return rtrim($dumper->dump($data, true)); + } + + private function prepareExpectation($expected, int $filter): string + { + if (!\is_string($expected)) { + $expected = $this->getDump($expected, null, $filter); + } + + return rtrim($expected); + } +} diff --git a/vendor/symfony/var-dumper/VarDumper.php b/vendor/symfony/var-dumper/VarDumper.php new file mode 100644 index 0000000..20429ac --- /dev/null +++ b/vendor/symfony/var-dumper/VarDumper.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextualizedDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\ServerDumper; + +// Load the global dump() function +require_once __DIR__.'/Resources/functions/dump.php'; + +/** + * @author Nicolas Grekas + */ +class VarDumper +{ + /** + * @var callable|null + */ + private static $handler; + + public static function dump($var) + { + if (null === self::$handler) { + self::register(); + } + + return (self::$handler)($var); + } + + /** + * @return callable|null + */ + public static function setHandler(callable $callable = null) + { + $prevHandler = self::$handler; + + // Prevent replacing the handler with expected format as soon as the env var was set: + if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { + return $prevHandler; + } + + self::$handler = $callable; + + return $prevHandler; + } + + private static function register(): void + { + $cloner = new VarCloner(); + $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + + $format = $_SERVER['VAR_DUMPER_FORMAT'] ?? null; + switch (true) { + case 'html' === $format: + $dumper = new HtmlDumper(); + break; + case 'cli' === $format: + $dumper = new CliDumper(); + break; + case 'server' === $format: + case $format && 'tcp' === parse_url($format, \PHP_URL_SCHEME): + $host = 'server' === $format ? $_SERVER['VAR_DUMPER_SERVER'] ?? '127.0.0.1:9912' : $format; + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper(); + $dumper = new ServerDumper($host, $dumper, self::getDefaultContextProviders()); + break; + default: + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper(); + } + + if (!$dumper instanceof ServerDumper) { + $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); + } + + self::$handler = function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }; + } + + private static function getDefaultContextProviders(): array + { + $contextProviders = []; + + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && class_exists(Request::class)) { + $requestStack = new RequestStack(); + $requestStack->push(Request::createFromGlobals()); + $contextProviders['request'] = new RequestContextProvider($requestStack); + } + + $fileLinkFormatter = class_exists(FileLinkFormatter::class) ? new FileLinkFormatter(null, $requestStack ?? null) : null; + + return $contextProviders + [ + 'cli' => new CliContextProvider(), + 'source' => new SourceContextProvider(null, null, $fileLinkFormatter), + ]; + } +} diff --git a/vendor/symfony/var-dumper/composer.json b/vendor/symfony/var-dumper/composer.json new file mode 100644 index 0000000..dc46f58 --- /dev/null +++ b/vendor/symfony/var-dumper/composer.json @@ -0,0 +1,50 @@ +{ + "name": "symfony/var-dumper", + "type": "library", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "keywords": ["dump", "debug"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "autoload": { + "files": [ "Resources/functions/dump.php" ], + "psr-4": { "Symfony\\Component\\VarDumper\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "minimum-stability": "dev" +} diff --git a/vendor/tightenco/collect/.github/workflows/run-tests.yml b/vendor/tightenco/collect/.github/workflows/run-tests.yml new file mode 100644 index 0000000..2a0d42f --- /dev/null +++ b/vendor/tightenco/collect/.github/workflows/run-tests.yml @@ -0,0 +1,41 @@ +name: Run tests + +on: + push: + branches: [laravel-9-ongoing, laravel-8-ongoing] + pull_request: + +jobs: + tests: + strategy: + matrix: + os: [Ubuntu, macOS] + php: [7.3, 7.4, 8.0, 8.1] + + include: + - os: Ubuntu + os-version: ubuntu-latest + + - os: macOS + os-version: macos-latest + + name: ${{ matrix.os }} - PHP ${{ matrix.php }} + + runs-on: ${{ matrix.os-version }} + + steps: + - name: Checkout code + uses: actions/checkout@v1 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: posix, dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick + coverage: none + + - name: Install dependencies + run: composer update --prefer-stable --prefer-dist --no-interaction + + - name: Run tests + run: bash upgrade.sh diff --git a/vendor/tightenco/collect/branch-commit-push.sh b/vendor/tightenco/collect/branch-commit-push.sh new file mode 100755 index 0000000..124e583 --- /dev/null +++ b/vendor/tightenco/collect/branch-commit-push.sh @@ -0,0 +1,67 @@ +#!/bin/bash + + +GREEN='\033[0;32m' +RED='\033[0;31m' +WHITE='\033[0;37m' +RESET='\033[0m' + +function validateVersion() +{ + echo "" + passedVersion=$1 + echo -e "${WHITE}-- Validating tag '$passedVersion'...${RESET}" + + # Todo: validate the version here using a regex; if fail, just exit + # ... expect 8.75.0, with no v in front of it + + if [[ $passedVersion == '' ]]; then + echo -e "\n-- Invalid tag. Tags should be structured without v; e.g. 8.57.0" + exit + fi + + echo -e "${WHITE}-- Tag valid.${RESET}" + echo "" +} + +# Exit script if any command fails (e.g. phpunit) +set -e + + +# Require confirmation it's set up corrctly +echo +echo -e "${WHITE}-- This script is meant to be run after running upgrade.sh, BEFORE committing to Git.${RESET}" + +while true; do + echo -e "${GREEN}-- Is that the current state of your local project?${RESET}" + read -p "-- (y/n) " yn + case $yn in + [Yy]* ) break;; + [Nn]* ) exit;; + * ) echo "Please answer y or n.";; + esac +done + +# Get the version and exit if not valid +validateVersion $1 + +# Create official v prefaced version +version="v$1" + +# Run tests (and bail if they fail) +phpunit +echo -e "\n${WHITE}-- Tests succeeded.${RESET}" + +# Branch +echo -e "\n${WHITE}-- Creating a Git branch '$version-changes'...${RESET}\n" +git checkout -b $version-changes + +# Add and commit, with "v8.57.0 changes" as the commit name +git add -A +git commit -m "$version changes" + +echo +echo -e "${WHITE}-- Git committed.${RESET}" + +# Push +git push -u origin $version-changes diff --git a/vendor/tightenco/collect/composer.json b/vendor/tightenco/collect/composer.json new file mode 100644 index 0000000..88ebb77 --- /dev/null +++ b/vendor/tightenco/collect/composer.json @@ -0,0 +1,51 @@ +{ + "name": "tightenco/collect", + "description": "Collect - Illuminate Collections as a separate package.", + "keywords": ["laravel", "collection"], + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": "^7.3|^8.0", + "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^8.3", + "nesbot/carbon": "^2.23.0" + }, + "autoload": { + "files": [ + "src/Collect/Support/helpers.php", + "src/Collect/Support/alias.php" + ], + "psr-4": { + "Tightenco\\Collect\\": "src/Collect" + } + }, + "autoload-dev": { + "files": [ + "tests/files/Support/Carbon.php", + "tests/files/Support/HtmlString.php", + "tests/files/Support/HigherOrderTapProxy.php", + "tests/files/Support/Str.php", + "tests/files/Support/Traits/Conditionable.php", + "tests/files/Support/Stringable.php", + "tests/files/Support/ItemNotFoundException.php", + "tests/files/Support/MultipleItemsFoundException.php", + "tests/Support/Concerns/CountsEnumerations.php" + ] + }, + "scripts": { + "test": [ + "@composer install", + "phpunit" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/vendor/tightenco/collect/framework-.zip b/vendor/tightenco/collect/framework-.zip new file mode 100644 index 0000000..e69de29 diff --git a/vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php b/vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php new file mode 100755 index 0000000..a205804 --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php @@ -0,0 +1,13 @@ +all(); + } elseif (! is_array($values)) { + continue; + } + + $results[] = $values; + } + + return array_merge([], ...$results); + } + + /** + * Cross join the given arrays, returning all possible permutations. + * + * @param iterable ...$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 iterable $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; + } + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @param iterable $array + * @return array + */ + public static function undot($array) + { + $results = []; + + foreach ($array as $key => $value) { + static::set($results, $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 Enumerable) { + return $array->has($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 iterable $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 ($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 iterable $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; + } else { + $values = $depth === 1 + ? array_values($item) + : static::flatten($item, $depth - 1); + + foreach ($values as $value) { + $result[] = $value; + } + } + } + + 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|int|null $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; + } + + /** + * Determine if any of the keys exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function hasAny($array, $keys) + { + if (is_null($keys)) { + return false; + } + + $keys = (array) $keys; + + if (! $array) { + return false; + } + + if ($keys === []) { + return false; + } + + foreach ($keys as $key) { + if (static::has($array, $key)) { + return true; + } + } + + return false; + } + + /** + * 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; + } + + /** + * Determines if an array is a list. + * + * An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between. + * + * @param array $array + * @return bool + */ + public static function isList($array) + { + return ! self::isAssoc($array); + } + + /** + * 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 iterable $array + * @param string|array|int|null $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 (func_num_args() == 2) { + 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|int $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; + } + + /** + * Convert the array into a query string. + * + * @param array $array + * @return string + */ + public static function query($array) + { + return http_build_query($array, '', '&', PHP_QUERY_RFC3986); + } + + /** + * Get one or a specified number of random values from an array. + * + * @param array $array + * @param int|null $number + * @param bool|false $preserveKeys + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random($array, $number = null, $preserveKeys = false) + { + $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 = []; + + if ($preserveKeys) { + foreach ((array) $keys as $key) { + $results[$key] = $array[$key]; + } + } else { + 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|null $key + * @param mixed $value + * @return array + */ + public static function set(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + + $keys = explode('.', $key); + + foreach ($keys as $i => $key) { + if (count($keys) === 1) { + break; + } + + unset($keys[$i]); + + // 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 { + mt_srand($seed); + shuffle($array); + mt_srand(); + } + + return $array; + } + + /** + * Sort the array using the given callback or "dot" notation. + * + * @param array $array + * @param callable|array|string|null $callback + * @return array + */ + public static function sort($array, $callback = null) + { + return Collection::make($array)->sortBy($callback)->all(); + } + + /** + * Recursively sort an array by keys and values. + * + * @param array $array + * @param int $options + * @param bool $descending + * @return array + */ + public static function sortRecursive($array, $options = SORT_REGULAR, $descending = false) + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = static::sortRecursive($value, $options, $descending); + } + } + + if (static::isAssoc($array)) { + $descending + ? krsort($array, $options) + : ksort($array, $options); + } else { + $descending + ? rsort($array, $options) + : sort($array, $options); + } + + return $array; + } + + /** + * Conditionally compile classes from an array into a CSS class list. + * + * @param array $array + * @return string + */ + public static function toCssClasses($array) + { + $classList = static::wrap($array); + + $classes = []; + + foreach ($classList as $class => $constraint) { + if (is_numeric($class)) { + $classes[] = $constraint; + } elseif ($constraint) { + $classes[] = $class; + } + } + + return implode(' ', $classes); + } + + /** + * 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); + } + + /** + * Filter items where the value is not null. + * + * @param array $array + * @return array + */ + public static function whereNotNull($array) + { + return static::where($array, function ($value) { + return ! is_null($value); + }); + } + + /** + * 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]; + } +} diff --git a/vendor/tightenco/collect/src/Collect/Support/Collection.php b/vendor/tightenco/collect/src/Collect/Support/Collection.php new file mode 100644 index 0000000..0dc7323 --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Support/Collection.php @@ -0,0 +1,1672 @@ +items = $this->getArrayableItems($items); + } + + /** + * Create a collection with the given range. + * + * @param int $from + * @param int $to + * @return static + */ + public static function range($from, $to) + { + return new static(range($from, $to)); + } + + /** + * Get all of the items in the collection. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Get a lazy collection for the items in this collection. + * + * @return \Tightenco\Collect\Support\LazyCollection + */ + public function lazy() + { + return new LazyCollection($this->items); + } + + /** + * Get the average value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function avg($callback = null) + { + $callback = $this->valueRetriever($callback); + + $items = $this->map(function ($value) use ($callback) { + return $callback($value); + })->filter(function ($value) { + return ! is_null($value); + }); + + if ($count = $items->count()) { + return $items->sum() / $count; + } + } + + /** + * Get the median of a given key. + * + * @param string|array|null $key + * @return mixed + */ + public function median($key = null) + { + $values = (isset($key) ? $this->pluck($key) : $this) + ->filter(function ($item) { + return ! is_null($item); + })->sort()->values(); + + $count = $values->count(); + + if ($count === 0) { + return; + } + + $middle = (int) ($count / 2); + + if ($count % 2) { + return $values->get($middle); + } + + return (new static([ + $values->get($middle - 1), $values->get($middle), + ]))->average(); + } + + /** + * Get the mode of a given key. + * + * @param string|array|null $key + * @return array|null + */ + public function mode($key = null) + { + if ($this->count() === 0) { + return; + } + + $collection = isset($key) ? $this->pluck($key) : $this; + + $counts = new static; + + $collection->each(function ($value) use ($counts) { + $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1; + }); + + $sorted = $counts->sort(); + + $highestValue = $sorted->last(); + + return $sorted->filter(function ($value) use ($highestValue) { + return $value == $highestValue; + })->sort()->keys()->all(); + } + + /** + * Collapse the collection of items into a single array. + * + * @return static + */ + public function collapse() + { + return new static(Arr::collapse($this->items)); + } + + /** + * Determine if an item exists in the collection. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function contains($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + if ($this->useAsCallable($key)) { + $placeholder = new stdClass; + + return $this->first($key, $placeholder) !== $placeholder; + } + + return in_array($key, $this->items); + } + + return $this->contains($this->operatorForWhere(...func_get_args())); + } + + /** + * Determine if an item is not contained in the collection. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function doesntContain($key, $operator = null, $value = null) + { + return ! $this->contains(...func_get_args()); + } + + /** + * Cross join with the given lists, returning all possible permutations. + * + * @param mixed ...$lists + * @return static + */ + public function crossJoin(...$lists) + { + return new static(Arr::crossJoin( + $this->items, ...array_map([$this, 'getArrayableItems'], $lists) + )); + } + + /** + * Get the items in the collection that are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection that are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffUsing($items, callable $callback) + { + return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Get the items in the collection whose keys and values are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffAssoc($items) + { + return new static(array_diff_assoc($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection whose keys and values are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffAssocUsing($items, callable $callback) + { + return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Get the items in the collection whose keys are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffKeys($items) + { + return new static(array_diff_key($this->items, $this->getArrayableItems($items))); + } + + /** + * Get the items in the collection whose keys are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffKeysUsing($items, callable $callback) + { + return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback)); + } + + /** + * Retrieve duplicate items from the collection. + * + * @param callable|string|null $callback + * @param bool $strict + * @return static + */ + public function duplicates($callback = null, $strict = false) + { + $items = $this->map($this->valueRetriever($callback)); + + $uniqueItems = $items->unique(null, $strict); + + $compare = $this->duplicateComparator($strict); + + $duplicates = new static; + + foreach ($items as $key => $value) { + if ($uniqueItems->isNotEmpty() && $compare($value, $uniqueItems->first())) { + $uniqueItems->shift(); + } else { + $duplicates[$key] = $value; + } + } + + return $duplicates; + } + + /** + * Retrieve duplicate items from the collection using strict comparison. + * + * @param callable|string|null $callback + * @return static + */ + public function duplicatesStrict($callback = null) + { + return $this->duplicates($callback, true); + } + + /** + * Get the comparison function to detect duplicates. + * + * @param bool $strict + * @return \Closure + */ + protected function duplicateComparator($strict) + { + if ($strict) { + return function ($a, $b) { + return $a === $b; + }; + } + + return function ($a, $b) { + return $a == $b; + }; + } + + /** + * Get all items except for those with the specified keys. + * + * @param \Tightenco\Collect\Support\Collection|mixed $keys + * @return static + */ + public function except($keys) + { + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } elseif (! is_array($keys)) { + $keys = func_get_args(); + } + + return new static(Arr::except($this->items, $keys)); + } + + /** + * Run a filter over each of the items. + * + * @param callable|null $callback + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(Arr::where($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * Get the first item from the collection passing the given truth test. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + return Arr::first($this->items, $callback, $default); + } + + /** + * Get a flattened array of the items in the collection. + * + * @param int $depth + * @return static + */ + public function flatten($depth = INF) + { + return new static(Arr::flatten($this->items, $depth)); + } + + /** + * Flip the items in the collection. + * + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * Remove an item from the collection by key. + * + * @param string|int|array $keys + * @return $this + */ + public function forget($keys) + { + foreach ((array) $keys as $key) { + $this->offsetUnset($key); + } + + return $this; + } + + /** + * Get an item from the collection by key. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->items)) { + return $this->items[$key]; + } + + return value($default); + } + + /** + * Get an item from the collection by key or add it to collection if it does not exist. + * + * @param mixed $key + * @param mixed $value + * @return mixed + */ + public function getOrPut($key, $value) + { + if (array_key_exists($key, $this->items)) { + return $this->items[$key]; + } + + $this->offsetSet($key, $value = value($value)); + + return $value; + } + + /** + * Group an associative array by a field or using a callback. + * + * @param array|callable|string $groupBy + * @param bool $preserveKeys + * @return static + */ + public function groupBy($groupBy, $preserveKeys = false) + { + if (! $this->useAsCallable($groupBy) && is_array($groupBy)) { + $nextGroups = $groupBy; + + $groupBy = array_shift($nextGroups); + } + + $groupBy = $this->valueRetriever($groupBy); + + $results = []; + + foreach ($this->items as $key => $value) { + $groupKeys = $groupBy($value, $key); + + if (! is_array($groupKeys)) { + $groupKeys = [$groupKeys]; + } + + foreach ($groupKeys as $groupKey) { + $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey; + + if (! array_key_exists($groupKey, $results)) { + $results[$groupKey] = new static; + } + + $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value); + } + } + + $result = new static($results); + + if (! empty($nextGroups)) { + return $result->map->groupBy($nextGroups, $preserveKeys); + } + + return $result; + } + + /** + * Key an associative array by a field or using a callback. + * + * @param callable|string $keyBy + * @return static + */ + public function keyBy($keyBy) + { + $keyBy = $this->valueRetriever($keyBy); + + $results = []; + + foreach ($this->items as $key => $item) { + $resolvedKey = $keyBy($item, $key); + + if (is_object($resolvedKey)) { + $resolvedKey = (string) $resolvedKey; + } + + $results[$resolvedKey] = $item; + } + + return new static($results); + } + + /** + * Determine if an item exists in the collection by key. + * + * @param mixed $key + * @return bool + */ + public function has($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if (! array_key_exists($value, $this->items)) { + return false; + } + } + + return true; + } + + /** + * Determine if any of the keys exist in the collection. + * + * @param mixed $key + * @return bool + */ + public function hasAny($key) + { + if ($this->isEmpty()) { + return false; + } + + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if ($this->has($value)) { + return true; + } + } + + return false; + } + + /** + * Concatenate values of a given key as a string. + * + * @param string $value + * @param string|null $glue + * @return string + */ + public function implode($value, $glue = null) + { + $first = $this->first(); + + if (is_array($first) || (is_object($first) && ! $first instanceof \Illuminate\Support\Stringable)) { + return implode($glue ?? '', $this->pluck($value)->all()); + } + + return implode($value ?? '', $this->items); + } + + /** + * Intersect the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->getArrayableItems($items))); + } + + /** + * Intersect the collection with the given items by key. + * + * @param mixed $items + * @return static + */ + public function intersectByKeys($items) + { + return new static(array_intersect_key( + $this->items, $this->getArrayableItems($items) + )); + } + + /** + * Determine if the collection is empty or not. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * Determine if the collection contains a single item. + * + * @return bool + */ + public function containsOneItem() + { + return $this->count() === 1; + } + + /** + * Join all items from the collection using a string. The final items can use a separate glue string. + * + * @param string $glue + * @param string $finalGlue + * @return string + */ + public function join($glue, $finalGlue = '') + { + if ($finalGlue === '') { + return $this->implode($glue); + } + + $count = $this->count(); + + if ($count === 0) { + return ''; + } + + if ($count === 1) { + return $this->last(); + } + + $collection = new static($this->items); + + $finalItem = $collection->pop(); + + return $collection->implode($glue).$finalGlue.$finalItem; + } + + /** + * Get the keys of the collection items. + * + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * Get the last item from the collection. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + return Arr::last($this->items, $callback, $default); + } + + /** + * Get the values of a given key. + * + * @param string|array|int|null $value + * @param string|null $key + * @return static + */ + public function pluck($value, $key = null) + { + return new static(Arr::pluck($this->items, $value, $key)); + } + + /** + * Run a map over each of the items. + * + * @param callable $callback + * @return static + */ + public function map(callable $callback) + { + $keys = array_keys($this->items); + + $items = array_map($callback, $this->items, $keys); + + return new static(array_combine($keys, $items)); + } + + /** + * Run a dictionary map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapToDictionary(callable $callback) + { + $dictionary = []; + + foreach ($this->items as $key => $item) { + $pair = $callback($item, $key); + + $key = key($pair); + + $value = reset($pair); + + if (! isset($dictionary[$key])) { + $dictionary[$key] = []; + } + + $dictionary[$key][] = $value; + } + + return new static($dictionary); + } + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapWithKeys(callable $callback) + { + $result = []; + + foreach ($this->items as $key => $value) { + $assoc = $callback($value, $key); + + foreach ($assoc as $mapKey => $mapValue) { + $result[$mapKey] = $mapValue; + } + } + + return new static($result); + } + + /** + * Merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->getArrayableItems($items))); + } + + /** + * Recursively merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function mergeRecursive($items) + { + return new static(array_merge_recursive($this->items, $this->getArrayableItems($items))); + } + + /** + * Create a collection by using this collection for keys and another for its values. + * + * @param mixed $values + * @return static + */ + public function combine($values) + { + return new static(array_combine($this->all(), $this->getArrayableItems($values))); + } + + /** + * Union the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function union($items) + { + return new static($this->items + $this->getArrayableItems($items)); + } + + /** + * Create a new collection consisting of every n-th element. + * + * @param int $step + * @param int $offset + * @return static + */ + public function nth($step, $offset = 0) + { + $new = []; + + $position = 0; + + foreach ($this->slice($offset)->items as $item) { + if ($position % $step === 0) { + $new[] = $item; + } + + $position++; + } + + return new static($new); + } + + /** + * Get the items with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function only($keys) + { + if (is_null($keys)) { + return new static($this->items); + } + + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } + + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::only($this->items, $keys)); + } + + /** + * Get and remove the last N items from the collection. + * + * @param int $count + * @return mixed + */ + public function pop($count = 1) + { + if ($count === 1) { + return array_pop($this->items); + } + + if ($this->isEmpty()) { + return new static; + } + + $results = []; + + $collectionCount = $this->count(); + + foreach (range(1, min($count, $collectionCount)) as $item) { + array_push($results, array_pop($this->items)); + } + + return new static($results); + } + + /** + * Push an item onto the beginning of the collection. + * + * @param mixed $value + * @param mixed $key + * @return $this + */ + public function prepend($value, $key = null) + { + $this->items = Arr::prepend($this->items, ...func_get_args()); + + return $this; + } + + /** + * Push one or more items onto the end of the collection. + * + * @param mixed $values + * @return $this + */ + public function push(...$values) + { + foreach ($values as $value) { + $this->items[] = $value; + } + + return $this; + } + + /** + * Push all of the given items onto the collection. + * + * @param iterable $source + * @return static + */ + public function concat($source) + { + $result = new static($this); + + foreach ($source as $item) { + $result->push($item); + } + + return $result; + } + + /** + * Get and remove an item from the collection. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function pull($key, $default = null) + { + return Arr::pull($this->items, $key, $default); + } + + /** + * Put an item in the collection by key. + * + * @param mixed $key + * @param mixed $value + * @return $this + */ + public function put($key, $value) + { + $this->offsetSet($key, $value); + + return $this; + } + + /** + * Get one or a specified number of items randomly from the collection. + * + * @param int|null $number + * @return static|mixed + * + * @throws \InvalidArgumentException + */ + public function random($number = null) + { + if (is_null($number)) { + return Arr::random($this->items); + } + + return new static(Arr::random($this->items, $number)); + } + + /** + * Replace the collection items with the given items. + * + * @param mixed $items + * @return static + */ + public function replace($items) + { + return new static(array_replace($this->items, $this->getArrayableItems($items))); + } + + /** + * Recursively replace the collection items with the given items. + * + * @param mixed $items + * @return static + */ + public function replaceRecursive($items) + { + return new static(array_replace_recursive($this->items, $this->getArrayableItems($items))); + } + + /** + * Reverse items order. + * + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items, true)); + } + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * @param mixed $value + * @param bool $strict + * @return mixed + */ + public function search($value, $strict = false) + { + if (! $this->useAsCallable($value)) { + return array_search($value, $this->items, $strict); + } + + foreach ($this->items as $key => $item) { + if ($value($item, $key)) { + return $key; + } + } + + return false; + } + + /** + * Get and remove the first N items from the collection. + * + * @param int $count + * @return mixed + */ + public function shift($count = 1) + { + if ($count === 1) { + return array_shift($this->items); + } + + if ($this->isEmpty()) { + return new static; + } + + $results = []; + + $collectionCount = $this->count(); + + foreach (range(1, min($count, $collectionCount)) as $item) { + array_push($results, array_shift($this->items)); + } + + return new static($results); + } + + /** + * Shuffle the items in the collection. + * + * @param int|null $seed + * @return static + */ + public function shuffle($seed = null) + { + return new static(Arr::shuffle($this->items, $seed)); + } + + /** + * Create chunks representing a "sliding window" view of the items in the collection. + * + * @param int $size + * @param int $step + * @return static + */ + public function sliding($size = 2, $step = 1) + { + $chunks = floor(($this->count() - $size) / $step) + 1; + + return static::times($chunks, function ($number) use ($size, $step) { + return $this->slice(($number - 1) * $step, $size); + }); + } + + /** + * Skip the first {$count} items. + * + * @param int $count + * @return static + */ + public function skip($count) + { + return $this->slice($count); + } + + /** + * Skip items in the collection until the given condition is met. + * + * @param mixed $value + * @return static + */ + public function skipUntil($value) + { + return new static($this->lazy()->skipUntil($value)->all()); + } + + /** + * Skip items in the collection while the given condition is met. + * + * @param mixed $value + * @return static + */ + public function skipWhile($value) + { + return new static($this->lazy()->skipWhile($value)->all()); + } + + /** + * Slice the underlying collection array. + * + * @param int $offset + * @param int|null $length + * @return static + */ + public function slice($offset, $length = null) + { + return new static(array_slice($this->items, $offset, $length, true)); + } + + /** + * Split a collection into a certain number of groups. + * + * @param int $numberOfGroups + * @return static + */ + public function split($numberOfGroups) + { + if ($this->isEmpty()) { + return new static; + } + + $groups = new static; + + $groupSize = floor($this->count() / $numberOfGroups); + + $remain = $this->count() % $numberOfGroups; + + $start = 0; + + for ($i = 0; $i < $numberOfGroups; $i++) { + $size = $groupSize; + + if ($i < $remain) { + $size++; + } + + if ($size) { + $groups->push(new static(array_slice($this->items, $start, $size))); + + $start += $size; + } + } + + return $groups; + } + + /** + * Split a collection into a certain number of groups, and fill the first groups completely. + * + * @param int $numberOfGroups + * @return static + */ + public function splitIn($numberOfGroups) + { + return $this->chunk(ceil($this->count() / $numberOfGroups)); + } + + /** + * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Tightenco\Collect\Support\ItemNotFoundException + * @throws \Tightenco\Collect\Support\MultipleItemsFoundException + */ + public function sole($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + $items = $this->when($filter)->filter($filter); + + if ($items->isEmpty()) { + throw new ItemNotFoundException; + } + + if ($items->count() > 1) { + throw new MultipleItemsFoundException; + } + + return $items->first(); + } + + /** + * Get the first item in the collection but throw an exception if no matching items exist. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Tightenco\Collect\Support\ItemNotFoundException + */ + public function firstOrFail($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + $placeholder = new stdClass(); + + $item = $this->first($filter, $placeholder); + + if ($item === $placeholder) { + throw new ItemNotFoundException; + } + + return $item; + } + + /** + * Chunk the collection into chunks of the given size. + * + * @param int $size + * @return static + */ + public function chunk($size) + { + if ($size <= 0) { + return new static; + } + + $chunks = []; + + foreach (array_chunk($this->items, $size, true) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * Chunk the collection into chunks with a callback. + * + * @param callable $callback + * @return static + */ + public function chunkWhile(callable $callback) + { + return new static( + $this->lazy()->chunkWhile($callback)->mapInto(static::class) + ); + } + + /** + * Sort through each item with a callback. + * + * @param callable|int|null $callback + * @return static + */ + public function sort($callback = null) + { + $items = $this->items; + + $callback && is_callable($callback) + ? uasort($items, $callback) + : asort($items, $callback ?? SORT_REGULAR); + + return new static($items); + } + + /** + * Sort items in descending order. + * + * @param int $options + * @return static + */ + public function sortDesc($options = SORT_REGULAR) + { + $items = $this->items; + + arsort($items, $options); + + return new static($items); + } + + /** + * Sort the collection using the given callback. + * + * @param callable|array|string $callback + * @param int $options + * @param bool $descending + * @return static + */ + public function sortBy($callback, $options = SORT_REGULAR, $descending = false) + { + if (is_array($callback) && ! is_callable($callback)) { + return $this->sortByMany($callback); + } + + $results = []; + + $callback = $this->valueRetriever($callback); + + // First we will loop through the items and get the comparator from a callback + // function which we were given. Then, we will sort the returned values and + // grab all the corresponding values for the sorted keys from this array. + foreach ($this->items as $key => $value) { + $results[$key] = $callback($value, $key); + } + + $descending ? arsort($results, $options) + : asort($results, $options); + + // Once we have sorted all of the keys in the array, we will loop through them + // and grab the corresponding model so we can set the underlying items list + // to the sorted version. Then we'll just return the collection instance. + foreach (array_keys($results) as $key) { + $results[$key] = $this->items[$key]; + } + + return new static($results); + } + + /** + * Sort the collection using multiple comparisons. + * + * @param array $comparisons + * @return static + */ + protected function sortByMany(array $comparisons = []) + { + $items = $this->items; + + usort($items, function ($a, $b) use ($comparisons) { + foreach ($comparisons as $comparison) { + $comparison = Arr::wrap($comparison); + + $prop = $comparison[0]; + + $ascending = Arr::get($comparison, 1, true) === true || + Arr::get($comparison, 1, true) === 'asc'; + + $result = 0; + + if (! is_string($prop) && is_callable($prop)) { + $result = $prop($a, $b); + } else { + $values = [data_get($a, $prop), data_get($b, $prop)]; + + if (! $ascending) { + $values = array_reverse($values); + } + + $result = $values[0] <=> $values[1]; + } + + if ($result === 0) { + continue; + } + + return $result; + } + }); + + return new static($items); + } + + /** + * Sort the collection in descending order using the given callback. + * + * @param callable|string $callback + * @param int $options + * @return static + */ + public function sortByDesc($callback, $options = SORT_REGULAR) + { + return $this->sortBy($callback, $options, true); + } + + /** + * Sort the collection keys. + * + * @param int $options + * @param bool $descending + * @return static + */ + public function sortKeys($options = SORT_REGULAR, $descending = false) + { + $items = $this->items; + + $descending ? krsort($items, $options) : ksort($items, $options); + + return new static($items); + } + + /** + * Sort the collection keys in descending order. + * + * @param int $options + * @return static + */ + public function sortKeysDesc($options = SORT_REGULAR) + { + return $this->sortKeys($options, true); + } + + /** + * Sort the collection keys using a callback. + * + * @param callable $callback + * @return static + */ + public function sortKeysUsing(callable $callback) + { + $items = $this->items; + + uksort($items, $callback); + + return new static($items); + } + + /** + * Splice a portion of the underlying collection array. + * + * @param int $offset + * @param int|null $length + * @param mixed $replacement + * @return static + */ + public function splice($offset, $length = null, $replacement = []) + { + if (func_num_args() === 1) { + return new static(array_splice($this->items, $offset)); + } + + return new static(array_splice($this->items, $offset, $length, $this->getArrayableItems($replacement))); + } + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return static + */ + public function take($limit) + { + if ($limit < 0) { + return $this->slice($limit, abs($limit)); + } + + return $this->slice(0, $limit); + } + + /** + * Take items in the collection until the given condition is met. + * + * @param mixed $value + * @return static + */ + public function takeUntil($value) + { + return new static($this->lazy()->takeUntil($value)->all()); + } + + /** + * Take items in the collection while the given condition is met. + * + * @param mixed $value + * @return static + */ + public function takeWhile($value) + { + return new static($this->lazy()->takeWhile($value)->all()); + } + + /** + * Transform each item in the collection using a callback. + * + * @param callable $callback + * @return $this + */ + public function transform(callable $callback) + { + $this->items = $this->map($callback)->all(); + + return $this; + } + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @return static + */ + public function undot() + { + return new static(Arr::undot($this->all())); + } + + /** + * Return only unique items from the collection array. + * + * @param string|callable|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false) + { + if (is_null($key) && $strict === false) { + return new static(array_unique($this->items, SORT_REGULAR)); + } + + $callback = $this->valueRetriever($key); + + $exists = []; + + return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) { + if (in_array($id = $callback($item, $key), $exists, $strict)) { + return true; + } + + $exists[] = $id; + }); + } + + /** + * Reset the keys on the underlying array. + * + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * Zip the collection together with one or more arrays. + * + * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @param mixed ...$items + * @return static + */ + public function zip($items) + { + $arrayableItems = array_map(function ($items) { + return $this->getArrayableItems($items); + }, func_get_args()); + + $params = array_merge([function () { + return new static(func_get_args()); + }, $this->items], $arrayableItems); + + return new static(array_map(...$params)); + } + + /** + * Pad collection to the specified length with a value. + * + * @param int $size + * @param mixed $value + * @return static + */ + public function pad($size, $value) + { + return new static(array_pad($this->items, $size, $value)); + } + + /** + * Get an iterator for the items. + * + * @return \ArrayIterator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return count($this->items); + } + + /** + * Count the number of items in the collection by a field or using a callback. + * + * @param callable|string $countBy + * @return static + */ + public function countBy($countBy = null) + { + return new static($this->lazy()->countBy($countBy)->all()); + } + + /** + * Add an item to the collection. + * + * @param mixed $item + * @return $this + */ + public function add($item) + { + $this->items[] = $item; + + return $this; + } + + /** + * Get a base Support collection instance from this collection. + * + * @return \Tightenco\Collect\Support\Collection + */ + public function toBase() + { + return new self($this); + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return isset($this->items[$key]); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->items[$key]; + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param mixed $key + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + unset($this->items[$key]); + } +} diff --git a/vendor/tightenco/collect/src/Collect/Support/Enumerable.php b/vendor/tightenco/collect/src/Collect/Support/Enumerable.php new file mode 100644 index 0000000..60af386 --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Support/Enumerable.php @@ -0,0 +1,1027 @@ +zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @param mixed ...$items + * @return static + */ + public function zip($items); + + /** + * Collect the values into a collection. + * + * @return \Tightenco\Collect\Support\Collection + */ + public function collect(); + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString(); + + /** + * Add a method to the list of proxied methods. + * + * @param string $method + * @return void + */ + public static function proxy($method); + + /** + * Dynamically access collection proxies. + * + * @param string $key + * @return mixed + * + * @throws \Exception + */ + public function __get($key); +} diff --git a/vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php b/vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php new file mode 100644 index 0000000..01ac43f --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php @@ -0,0 +1,63 @@ +method = $method; + $this->collection = $collection; + } + + /** + * Proxy accessing an attribute onto the collection items. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->collection->{$this->method}(function ($value) use ($key) { + return is_array($value) ? $value[$key] : $value->{$key}; + }); + } + + /** + * Proxy a method call onto the collection items. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->collection->{$this->method}(function ($value) use ($method, $parameters) { + return $value->{$method}(...$parameters); + }); + } +} diff --git a/vendor/tightenco/collect/src/Collect/Support/HigherOrderWhenProxy.php b/vendor/tightenco/collect/src/Collect/Support/HigherOrderWhenProxy.php new file mode 100644 index 0000000..ea48c7c --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Support/HigherOrderWhenProxy.php @@ -0,0 +1,63 @@ +condition = $condition; + $this->collection = $collection; + } + + /** + * Proxy accessing an attribute onto the collection. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->condition + ? $this->collection->{$key} + : $this->collection; + } + + /** + * Proxy a method call onto the collection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->condition + ? $this->collection->{$method}(...$parameters) + : $this->collection; + } +} diff --git a/vendor/tightenco/collect/src/Collect/Support/LazyCollection.php b/vendor/tightenco/collect/src/Collect/Support/LazyCollection.php new file mode 100644 index 0000000..a54787d --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Support/LazyCollection.php @@ -0,0 +1,1585 @@ +source = $source; + } elseif (is_null($source)) { + $this->source = static::empty(); + } else { + $this->source = $this->getArrayableItems($source); + } + } + + /** + * Create a collection with the given range. + * + * @param int $from + * @param int $to + * @return static + */ + public static function range($from, $to) + { + return new static(function () use ($from, $to) { + if ($from <= $to) { + for (; $from <= $to; $from++) { + yield $from; + } + } else { + for (; $from >= $to; $from--) { + yield $from; + } + } + }); + } + + /** + * Get all items in the enumerable. + * + * @return array + */ + public function all() + { + if (is_array($this->source)) { + return $this->source; + } + + return iterator_to_array($this->getIterator()); + } + + /** + * Eager load all items into a new lazy collection backed by an array. + * + * @return static + */ + public function eager() + { + return new static($this->all()); + } + + /** + * Cache values as they're enumerated. + * + * @return static + */ + public function remember() + { + $iterator = $this->getIterator(); + + $iteratorIndex = 0; + + $cache = []; + + return new static(function () use ($iterator, &$iteratorIndex, &$cache) { + for ($index = 0; true; $index++) { + if (array_key_exists($index, $cache)) { + yield $cache[$index][0] => $cache[$index][1]; + + continue; + } + + if ($iteratorIndex < $index) { + $iterator->next(); + + $iteratorIndex++; + } + + if (! $iterator->valid()) { + break; + } + + $cache[$index] = [$iterator->key(), $iterator->current()]; + + yield $cache[$index][0] => $cache[$index][1]; + } + }); + } + + /** + * Get the average value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function avg($callback = null) + { + return $this->collect()->avg($callback); + } + + /** + * Get the median of a given key. + * + * @param string|array|null $key + * @return mixed + */ + public function median($key = null) + { + return $this->collect()->median($key); + } + + /** + * Get the mode of a given key. + * + * @param string|array|null $key + * @return array|null + */ + public function mode($key = null) + { + return $this->collect()->mode($key); + } + + /** + * Collapse the collection of items into a single array. + * + * @return static + */ + public function collapse() + { + return new static(function () { + foreach ($this as $values) { + if (is_array($values) || $values instanceof Enumerable) { + foreach ($values as $value) { + yield $value; + } + } + } + }); + } + + /** + * Determine if an item exists in the enumerable. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function contains($key, $operator = null, $value = null) + { + if (func_num_args() === 1 && $this->useAsCallable($key)) { + $placeholder = new stdClass; + + return $this->first($key, $placeholder) !== $placeholder; + } + + if (func_num_args() === 1) { + $needle = $key; + + foreach ($this as $value) { + if ($value == $needle) { + return true; + } + } + + return false; + } + + return $this->contains($this->operatorForWhere(...func_get_args())); + } + + /** + * Determine if an item is not contained in the enumerable. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function doesntContain($key, $operator = null, $value = null) + { + return ! $this->contains(...func_get_args()); + } + + /** + * Cross join the given iterables, returning all possible permutations. + * + * @param array ...$arrays + * @return static + */ + public function crossJoin(...$arrays) + { + return $this->passthru('crossJoin', func_get_args()); + } + + /** + * Count the number of items in the collection by a field or using a callback. + * + * @param callable|string $countBy + * @return static + */ + public function countBy($countBy = null) + { + $countBy = is_null($countBy) + ? $this->identity() + : $this->valueRetriever($countBy); + + return new static(function () use ($countBy) { + $counts = []; + + foreach ($this as $key => $value) { + $group = $countBy($value, $key); + + if (empty($counts[$group])) { + $counts[$group] = 0; + } + + $counts[$group]++; + } + + yield from $counts; + }); + } + + /** + * Get the items that are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diff($items) + { + return $this->passthru('diff', func_get_args()); + } + + /** + * Get the items that are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffUsing($items, callable $callback) + { + return $this->passthru('diffUsing', func_get_args()); + } + + /** + * Get the items whose keys and values are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffAssoc($items) + { + return $this->passthru('diffAssoc', func_get_args()); + } + + /** + * Get the items whose keys and values are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffAssocUsing($items, callable $callback) + { + return $this->passthru('diffAssocUsing', func_get_args()); + } + + /** + * Get the items whose keys are not present in the given items. + * + * @param mixed $items + * @return static + */ + public function diffKeys($items) + { + return $this->passthru('diffKeys', func_get_args()); + } + + /** + * Get the items whose keys are not present in the given items, using the callback. + * + * @param mixed $items + * @param callable $callback + * @return static + */ + public function diffKeysUsing($items, callable $callback) + { + return $this->passthru('diffKeysUsing', func_get_args()); + } + + /** + * Retrieve duplicate items. + * + * @param callable|string|null $callback + * @param bool $strict + * @return static + */ + public function duplicates($callback = null, $strict = false) + { + return $this->passthru('duplicates', func_get_args()); + } + + /** + * Retrieve duplicate items using strict comparison. + * + * @param callable|string|null $callback + * @return static + */ + public function duplicatesStrict($callback = null) + { + return $this->passthru('duplicatesStrict', func_get_args()); + } + + /** + * Get all items except for those with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function except($keys) + { + return $this->passthru('except', func_get_args()); + } + + /** + * Run a filter over each of the items. + * + * @param callable|null $callback + * @return static + */ + public function filter(callable $callback = null) + { + if (is_null($callback)) { + $callback = function ($value) { + return (bool) $value; + }; + } + + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + if ($callback($value, $key)) { + yield $key => $value; + } + } + }); + } + + /** + * Get the first item from the enumerable passing the given truth test. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + $iterator = $this->getIterator(); + + if (is_null($callback)) { + if (! $iterator->valid()) { + return value($default); + } + + return $iterator->current(); + } + + foreach ($iterator as $key => $value) { + if ($callback($value, $key)) { + return $value; + } + } + + return value($default); + } + + /** + * Get a flattened list of the items in the collection. + * + * @param int $depth + * @return static + */ + public function flatten($depth = INF) + { + $instance = new static(function () use ($depth) { + foreach ($this as $item) { + if (! is_array($item) && ! $item instanceof Enumerable) { + yield $item; + } elseif ($depth === 1) { + yield from $item; + } else { + yield from (new static($item))->flatten($depth - 1); + } + } + }); + + return $instance->values(); + } + + /** + * Flip the items in the collection. + * + * @return static + */ + public function flip() + { + return new static(function () { + foreach ($this as $key => $value) { + yield $value => $key; + } + }); + } + + /** + * Get an item by key. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (is_null($key)) { + return; + } + + foreach ($this as $outerKey => $outerValue) { + if ($outerKey == $key) { + return $outerValue; + } + } + + return value($default); + } + + /** + * Group an associative array by a field or using a callback. + * + * @param array|callable|string $groupBy + * @param bool $preserveKeys + * @return static + */ + public function groupBy($groupBy, $preserveKeys = false) + { + return $this->passthru('groupBy', func_get_args()); + } + + /** + * Key an associative array by a field or using a callback. + * + * @param callable|string $keyBy + * @return static + */ + public function keyBy($keyBy) + { + return new static(function () use ($keyBy) { + $keyBy = $this->valueRetriever($keyBy); + + foreach ($this as $key => $item) { + $resolvedKey = $keyBy($item, $key); + + if (is_object($resolvedKey)) { + $resolvedKey = (string) $resolvedKey; + } + + yield $resolvedKey => $item; + } + }); + } + + /** + * Determine if an item exists in the collection by key. + * + * @param mixed $key + * @return bool + */ + public function has($key) + { + $keys = array_flip(is_array($key) ? $key : func_get_args()); + $count = count($keys); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $keys) && --$count == 0) { + return true; + } + } + + return false; + } + + /** + * Determine if any of the keys exist in the collection. + * + * @param mixed $key + * @return bool + */ + public function hasAny($key) + { + $keys = array_flip(is_array($key) ? $key : func_get_args()); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $keys)) { + return true; + } + } + + return false; + } + + /** + * Concatenate values of a given key as a string. + * + * @param string $value + * @param string|null $glue + * @return string + */ + public function implode($value, $glue = null) + { + return $this->collect()->implode(...func_get_args()); + } + + /** + * Intersect the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function intersect($items) + { + return $this->passthru('intersect', func_get_args()); + } + + /** + * Intersect the collection with the given items by key. + * + * @param mixed $items + * @return static + */ + public function intersectByKeys($items) + { + return $this->passthru('intersectByKeys', func_get_args()); + } + + /** + * Determine if the items are empty or not. + * + * @return bool + */ + public function isEmpty() + { + return ! $this->getIterator()->valid(); + } + + /** + * Determine if the collection contains a single item. + * + * @return bool + */ + public function containsOneItem() + { + return $this->take(2)->count() === 1; + } + + /** + * Join all items from the collection using a string. The final items can use a separate glue string. + * + * @param string $glue + * @param string $finalGlue + * @return string + */ + public function join($glue, $finalGlue = '') + { + return $this->collect()->join(...func_get_args()); + } + + /** + * Get the keys of the collection items. + * + * @return static + */ + public function keys() + { + return new static(function () { + foreach ($this as $key => $value) { + yield $key; + } + }); + } + + /** + * Get the last item from the collection. + * + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + $needle = $placeholder = new stdClass; + + foreach ($this as $key => $value) { + if (is_null($callback) || $callback($value, $key)) { + $needle = $value; + } + } + + return $needle === $placeholder ? value($default) : $needle; + } + + /** + * Get the values of a given key. + * + * @param string|array $value + * @param string|null $key + * @return static + */ + public function pluck($value, $key = null) + { + return new static(function () use ($value, $key) { + [$value, $key] = $this->explodePluckParameters($value, $key); + + foreach ($this as $item) { + $itemValue = data_get($item, $value); + + if (is_null($key)) { + yield $itemValue; + } else { + $itemKey = data_get($item, $key); + + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + + yield $itemKey => $itemValue; + } + } + }); + } + + /** + * Run a map over each of the items. + * + * @param callable $callback + * @return static + */ + public function map(callable $callback) + { + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + yield $key => $callback($value, $key); + } + }); + } + + /** + * Run a dictionary map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapToDictionary(callable $callback) + { + return $this->passthru('mapToDictionary', func_get_args()); + } + + /** + * Run an associative map over each of the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapWithKeys(callable $callback) + { + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + yield from $callback($value, $key); + } + }); + } + + /** + * Merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function merge($items) + { + return $this->passthru('merge', func_get_args()); + } + + /** + * Recursively merge the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function mergeRecursive($items) + { + return $this->passthru('mergeRecursive', func_get_args()); + } + + /** + * Create a collection by using this collection for keys and another for its values. + * + * @param mixed $values + * @return static + */ + public function combine($values) + { + return new static(function () use ($values) { + $values = $this->makeIterator($values); + + $errorMessage = 'Both parameters should have an equal number of elements'; + + foreach ($this as $key) { + if (! $values->valid()) { + trigger_error($errorMessage, E_USER_WARNING); + + break; + } + + yield $key => $values->current(); + + $values->next(); + } + + if ($values->valid()) { + trigger_error($errorMessage, E_USER_WARNING); + } + }); + } + + /** + * Union the collection with the given items. + * + * @param mixed $items + * @return static + */ + public function union($items) + { + return $this->passthru('union', func_get_args()); + } + + /** + * Create a new collection consisting of every n-th element. + * + * @param int $step + * @param int $offset + * @return static + */ + public function nth($step, $offset = 0) + { + return new static(function () use ($step, $offset) { + $position = 0; + + foreach ($this->slice($offset) as $item) { + if ($position % $step === 0) { + yield $item; + } + + $position++; + } + }); + } + + /** + * Get the items with the specified keys. + * + * @param mixed $keys + * @return static + */ + public function only($keys) + { + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } elseif (! is_null($keys)) { + $keys = is_array($keys) ? $keys : func_get_args(); + } + + return new static(function () use ($keys) { + if (is_null($keys)) { + yield from $this; + } else { + $keys = array_flip($keys); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $keys)) { + yield $key => $value; + + unset($keys[$key]); + + if (empty($keys)) { + break; + } + } + } + } + }); + } + + /** + * Push all of the given items onto the collection. + * + * @param iterable $source + * @return static + */ + public function concat($source) + { + return (new static(function () use ($source) { + yield from $this; + yield from $source; + }))->values(); + } + + /** + * Get one or a specified number of items randomly from the collection. + * + * @param int|null $number + * @return static|mixed + * + * @throws \InvalidArgumentException + */ + public function random($number = null) + { + $result = $this->collect()->random(...func_get_args()); + + return is_null($number) ? $result : new static($result); + } + + /** + * Replace the collection items with the given items. + * + * @param mixed $items + * @return static + */ + public function replace($items) + { + return new static(function () use ($items) { + $items = $this->getArrayableItems($items); + + foreach ($this as $key => $value) { + if (array_key_exists($key, $items)) { + yield $key => $items[$key]; + + unset($items[$key]); + } else { + yield $key => $value; + } + } + + foreach ($items as $key => $value) { + yield $key => $value; + } + }); + } + + /** + * Recursively replace the collection items with the given items. + * + * @param mixed $items + * @return static + */ + public function replaceRecursive($items) + { + return $this->passthru('replaceRecursive', func_get_args()); + } + + /** + * Reverse items order. + * + * @return static + */ + public function reverse() + { + return $this->passthru('reverse', func_get_args()); + } + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * @param mixed $value + * @param bool $strict + * @return mixed + */ + public function search($value, $strict = false) + { + $predicate = $this->useAsCallable($value) + ? $value + : function ($item) use ($value, $strict) { + return $strict ? $item === $value : $item == $value; + }; + + foreach ($this as $key => $item) { + if ($predicate($item, $key)) { + return $key; + } + } + + return false; + } + + /** + * Shuffle the items in the collection. + * + * @param int|null $seed + * @return static + */ + public function shuffle($seed = null) + { + return $this->passthru('shuffle', func_get_args()); + } + + /** + * Create chunks representing a "sliding window" view of the items in the collection. + * + * @param int $size + * @param int $step + * @return static + */ + public function sliding($size = 2, $step = 1) + { + return new static(function () use ($size, $step) { + $iterator = $this->getIterator(); + + $chunk = []; + + while ($iterator->valid()) { + $chunk[$iterator->key()] = $iterator->current(); + + if (count($chunk) == $size) { + yield tap(new static($chunk), function () use (&$chunk, $step) { + $chunk = array_slice($chunk, $step, null, true); + }); + + // If the $step between chunks is bigger than each chunk's $size + // we will skip the extra items (which should never be in any + // chunk) before we continue to the next chunk in the loop. + if ($step > $size) { + $skip = $step - $size; + + for ($i = 0; $i < $skip && $iterator->valid(); $i++) { + $iterator->next(); + } + } + } + + $iterator->next(); + } + }); + } + + /** + * Skip the first {$count} items. + * + * @param int $count + * @return static + */ + public function skip($count) + { + return new static(function () use ($count) { + $iterator = $this->getIterator(); + + while ($iterator->valid() && $count--) { + $iterator->next(); + } + + while ($iterator->valid()) { + yield $iterator->key() => $iterator->current(); + + $iterator->next(); + } + }); + } + + /** + * Skip items in the collection until the given condition is met. + * + * @param mixed $value + * @return static + */ + public function skipUntil($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return $this->skipWhile($this->negate($callback)); + } + + /** + * Skip items in the collection while the given condition is met. + * + * @param mixed $value + * @return static + */ + public function skipWhile($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return new static(function () use ($callback) { + $iterator = $this->getIterator(); + + while ($iterator->valid() && $callback($iterator->current(), $iterator->key())) { + $iterator->next(); + } + + while ($iterator->valid()) { + yield $iterator->key() => $iterator->current(); + + $iterator->next(); + } + }); + } + + /** + * Get a slice of items from the enumerable. + * + * @param int $offset + * @param int|null $length + * @return static + */ + public function slice($offset, $length = null) + { + if ($offset < 0 || $length < 0) { + return $this->passthru('slice', func_get_args()); + } + + $instance = $this->skip($offset); + + return is_null($length) ? $instance : $instance->take($length); + } + + /** + * Split a collection into a certain number of groups. + * + * @param int $numberOfGroups + * @return static + */ + public function split($numberOfGroups) + { + return $this->passthru('split', func_get_args()); + } + + /** + * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Tightenco\Collect\Support\ItemNotFoundException + * @throws \Tightenco\Collect\Support\MultipleItemsFoundException + */ + public function sole($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->when($filter) + ->filter($filter) + ->take(2) + ->collect() + ->sole(); + } + + /** + * Get the first item in the collection but throw an exception if no matching items exist. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Tightenco\Collect\Support\ItemNotFoundException + */ + public function firstOrFail($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->when($filter) + ->filter($filter) + ->take(1) + ->collect() + ->firstOrFail(); + } + + /** + * Chunk the collection into chunks of the given size. + * + * @param int $size + * @return static + */ + public function chunk($size) + { + if ($size <= 0) { + return static::empty(); + } + + return new static(function () use ($size) { + $iterator = $this->getIterator(); + + while ($iterator->valid()) { + $chunk = []; + + while (true) { + $chunk[$iterator->key()] = $iterator->current(); + + if (count($chunk) < $size) { + $iterator->next(); + + if (! $iterator->valid()) { + break; + } + } else { + break; + } + } + + yield new static($chunk); + + $iterator->next(); + } + }); + } + + /** + * Split a collection into a certain number of groups, and fill the first groups completely. + * + * @param int $numberOfGroups + * @return static + */ + public function splitIn($numberOfGroups) + { + return $this->chunk(ceil($this->count() / $numberOfGroups)); + } + + /** + * Chunk the collection into chunks with a callback. + * + * @param callable $callback + * @return static + */ + public function chunkWhile(callable $callback) + { + return new static(function () use ($callback) { + $iterator = $this->getIterator(); + + $chunk = new Collection; + + if ($iterator->valid()) { + $chunk[$iterator->key()] = $iterator->current(); + + $iterator->next(); + } + + while ($iterator->valid()) { + if (! $callback($iterator->current(), $iterator->key(), $chunk)) { + yield new static($chunk); + + $chunk = new Collection; + } + + $chunk[$iterator->key()] = $iterator->current(); + + $iterator->next(); + } + + if ($chunk->isNotEmpty()) { + yield new static($chunk); + } + }); + } + + /** + * Sort through each item with a callback. + * + * @param callable|null|int $callback + * @return static + */ + public function sort($callback = null) + { + return $this->passthru('sort', func_get_args()); + } + + /** + * Sort items in descending order. + * + * @param int $options + * @return static + */ + public function sortDesc($options = SORT_REGULAR) + { + return $this->passthru('sortDesc', func_get_args()); + } + + /** + * Sort the collection using the given callback. + * + * @param callable|string $callback + * @param int $options + * @param bool $descending + * @return static + */ + public function sortBy($callback, $options = SORT_REGULAR, $descending = false) + { + return $this->passthru('sortBy', func_get_args()); + } + + /** + * Sort the collection in descending order using the given callback. + * + * @param callable|string $callback + * @param int $options + * @return static + */ + public function sortByDesc($callback, $options = SORT_REGULAR) + { + return $this->passthru('sortByDesc', func_get_args()); + } + + /** + * Sort the collection keys. + * + * @param int $options + * @param bool $descending + * @return static + */ + public function sortKeys($options = SORT_REGULAR, $descending = false) + { + return $this->passthru('sortKeys', func_get_args()); + } + + /** + * Sort the collection keys in descending order. + * + * @param int $options + * @return static + */ + public function sortKeysDesc($options = SORT_REGULAR) + { + return $this->passthru('sortKeysDesc', func_get_args()); + } + + /** + * Sort the collection keys using a callback. + * + * @param callable $callback + * @return static + */ + public function sortKeysUsing(callable $callback) + { + return $this->passthru('sortKeysUsing', func_get_args()); + } + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return static + */ + public function take($limit) + { + if ($limit < 0) { + return $this->passthru('take', func_get_args()); + } + + return new static(function () use ($limit) { + $iterator = $this->getIterator(); + + while ($limit--) { + if (! $iterator->valid()) { + break; + } + + yield $iterator->key() => $iterator->current(); + + if ($limit) { + $iterator->next(); + } + } + }); + } + + /** + * Take items in the collection until the given condition is met. + * + * @param mixed $value + * @return static + */ + public function takeUntil($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return new static(function () use ($callback) { + foreach ($this as $key => $item) { + if ($callback($item, $key)) { + break; + } + + yield $key => $item; + } + }); + } + + /** + * Take items in the collection until a given point in time. + * + * @param \DateTimeInterface $timeout + * @return static + */ + public function takeUntilTimeout(DateTimeInterface $timeout) + { + $timeout = $timeout->getTimestamp(); + + return $this->takeWhile(function () use ($timeout) { + return $this->now() < $timeout; + }); + } + + /** + * Take items in the collection while the given condition is met. + * + * @param mixed $value + * @return static + */ + public function takeWhile($value) + { + $callback = $this->useAsCallable($value) ? $value : $this->equality($value); + + return $this->takeUntil(function ($item, $key) use ($callback) { + return ! $callback($item, $key); + }); + } + + /** + * Pass each item in the collection to the given callback, lazily. + * + * @param callable $callback + * @return static + */ + public function tapEach(callable $callback) + { + return new static(function () use ($callback) { + foreach ($this as $key => $value) { + $callback($value, $key); + + yield $key => $value; + } + }); + } + + /** + * Convert a flatten "dot" notation array into an expanded array. + * + * @return static + */ + public function undot() + { + return $this->passthru('undot', []); + } + + /** + * Return only unique items from the collection array. + * + * @param string|callable|null $key + * @param bool $strict + * @return static + */ + public function unique($key = null, $strict = false) + { + $callback = $this->valueRetriever($key); + + return new static(function () use ($callback, $strict) { + $exists = []; + + foreach ($this as $key => $item) { + if (! in_array($id = $callback($item, $key), $exists, $strict)) { + yield $key => $item; + + $exists[] = $id; + } + } + }); + } + + /** + * Reset the keys on the underlying array. + * + * @return static + */ + public function values() + { + return new static(function () { + foreach ($this as $item) { + yield $item; + } + }); + } + + /** + * Zip the collection together with one or more arrays. + * + * e.g. new LazyCollection([1, 2, 3])->zip([4, 5, 6]); + * => [[1, 4], [2, 5], [3, 6]] + * + * @param mixed ...$items + * @return static + */ + public function zip($items) + { + $iterables = func_get_args(); + + return new static(function () use ($iterables) { + $iterators = Collection::make($iterables)->map(function ($iterable) { + return $this->makeIterator($iterable); + })->prepend($this->getIterator()); + + while ($iterators->contains->valid()) { + yield new static($iterators->map->current()); + + $iterators->each->next(); + } + }); + } + + /** + * Pad collection to the specified length with a value. + * + * @param int $size + * @param mixed $value + * @return static + */ + public function pad($size, $value) + { + if ($size < 0) { + return $this->passthru('pad', func_get_args()); + } + + return new static(function () use ($size, $value) { + $yielded = 0; + + foreach ($this as $index => $item) { + yield $index => $item; + + $yielded++; + } + + while ($yielded++ < $size) { + yield $value; + } + }); + } + + /** + * Get the values iterator. + * + * @return \Traversable + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + return $this->makeIterator($this->source); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + if (is_array($this->source)) { + return count($this->source); + } + + return iterator_count($this->getIterator()); + } + + /** + * Make an iterator from the given source. + * + * @param mixed $source + * @return \Traversable + */ + protected function makeIterator($source) + { + if ($source instanceof IteratorAggregate) { + return $source->getIterator(); + } + + if (is_array($source)) { + return new ArrayIterator($source); + } + + return $source(); + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|array $value + * @param string|array|null $key + * @return array + */ + protected function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Pass this lazy collection through a method on the collection class. + * + * @param string $method + * @param array $params + * @return static + */ + protected function passthru($method, array $params) + { + return new static(function () use ($method, $params) { + yield from $this->collect()->$method(...$params); + }); + } + + /** + * Get the current time. + * + * @return int + */ + protected function now() + { + return time(); + } +} diff --git a/vendor/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php b/vendor/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php new file mode 100644 index 0000000..2f1187f --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php @@ -0,0 +1,1116 @@ +all() : $value; + } + + /** + * Create a new instance with no items. + * + * @return static + */ + public static function empty() + { + return new static([]); + } + + /** + * Create a new collection by invoking the callback a given amount of times. + * + * @param int $number + * @param callable|null $callback + * @return static + */ + public static function times($number, callable $callback = null) + { + if ($number < 1) { + return new static; + } + + return static::range(1, $number) + ->when($callback) + ->map($callback); + } + + /** + * Alias for the "avg" method. + * + * @param callable|string|null $callback + * @return mixed + */ + public function average($callback = null) + { + return $this->avg($callback); + } + + /** + * Alias for the "contains" method. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function some($key, $operator = null, $value = null) + { + return $this->contains(...func_get_args()); + } + + /** + * Determine if an item exists, using strict comparison. + * + * @param mixed $key + * @param mixed $value + * @return bool + */ + public function containsStrict($key, $value = null) + { + if (func_num_args() === 2) { + return $this->contains(function ($item) use ($key, $value) { + return data_get($item, $key) === $value; + }); + } + + if ($this->useAsCallable($key)) { + return ! is_null($this->first($key)); + } + + foreach ($this as $item) { + if ($item === $key) { + return true; + } + } + + return false; + } + + /** + * Dump the items and end the script. + * + * @param mixed ...$args + * @return void + */ + public function dd(...$args) + { + $this->dump(...$args); + + exit(1); + } + + /** + * Dump the items. + * + * @return $this + */ + public function dump() + { + (new Collection(func_get_args())) + ->push($this->all()) + ->each(function ($item) { + VarDumper::dump($item); + }); + + return $this; + } + + /** + * Execute a callback over each item. + * + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this as $key => $item) { + if ($callback($item, $key) === false) { + break; + } + } + + return $this; + } + + /** + * Execute a callback over each nested chunk of items. + * + * @param callable $callback + * @return static + */ + public function eachSpread(callable $callback) + { + return $this->each(function ($chunk, $key) use ($callback) { + $chunk[] = $key; + + return $callback(...$chunk); + }); + } + + /** + * Determine if all items pass the given truth test. + * + * @param string|callable $key + * @param mixed $operator + * @param mixed $value + * @return bool + */ + public function every($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + $callback = $this->valueRetriever($key); + + foreach ($this as $k => $v) { + if (! $callback($v, $k)) { + return false; + } + } + + return true; + } + + return $this->every($this->operatorForWhere(...func_get_args())); + } + + /** + * Get the first item by the given key value pair. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return mixed + */ + public function firstWhere($key, $operator = null, $value = null) + { + return $this->first($this->operatorForWhere(...func_get_args())); + } + + /** + * Determine if the collection is not empty. + * + * @return bool + */ + public function isNotEmpty() + { + return ! $this->isEmpty(); + } + + /** + * Run a map over each nested chunk of items. + * + * @param callable $callback + * @return static + */ + public function mapSpread(callable $callback) + { + return $this->map(function ($chunk, $key) use ($callback) { + $chunk[] = $key; + + return $callback(...$chunk); + }); + } + + /** + * Run a grouping map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @param callable $callback + * @return static + */ + public function mapToGroups(callable $callback) + { + $groups = $this->mapToDictionary($callback); + + return $groups->map([$this, 'make']); + } + + /** + * Map a collection and flatten the result by a single level. + * + * @param callable $callback + * @return static + */ + public function flatMap(callable $callback) + { + return $this->map($callback)->collapse(); + } + + /** + * Map the values into a new class. + * + * @param string $class + * @return static + */ + public function mapInto($class) + { + return $this->map(function ($value, $key) use ($class) { + return new $class($value, $key); + }); + } + + /** + * Get the min value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function min($callback = null) + { + $callback = $this->valueRetriever($callback); + + return $this->map(function ($value) use ($callback) { + return $callback($value); + })->filter(function ($value) { + return ! is_null($value); + })->reduce(function ($result, $value) { + return is_null($result) || $value < $result ? $value : $result; + }); + } + + /** + * Get the max value of a given key. + * + * @param callable|string|null $callback + * @return mixed + */ + public function max($callback = null) + { + $callback = $this->valueRetriever($callback); + + return $this->filter(function ($value) { + return ! is_null($value); + })->reduce(function ($result, $item) use ($callback) { + $value = $callback($item); + + return is_null($result) || $value > $result ? $value : $result; + }); + } + + /** + * "Paginate" the collection by slicing it into a smaller collection. + * + * @param int $page + * @param int $perPage + * @return static + */ + public function forPage($page, $perPage) + { + $offset = max(0, ($page - 1) * $perPage); + + return $this->slice($offset, $perPage); + } + + /** + * Partition the collection into two arrays using the given callback or key. + * + * @param callable|string $key + * @param mixed $operator + * @param mixed $value + * @return static + */ + public function partition($key, $operator = null, $value = null) + { + $passed = []; + $failed = []; + + $callback = func_num_args() === 1 + ? $this->valueRetriever($key) + : $this->operatorForWhere(...func_get_args()); + + foreach ($this as $key => $item) { + if ($callback($item, $key)) { + $passed[$key] = $item; + } else { + $failed[$key] = $item; + } + } + + return new static([new static($passed), new static($failed)]); + } + + /** + * Get the sum of the given values. + * + * @param callable|string|null $callback + * @return mixed + */ + public function sum($callback = null) + { + $callback = is_null($callback) + ? $this->identity() + : $this->valueRetriever($callback); + + return $this->reduce(function ($result, $item) use ($callback) { + return $result + $callback($item); + }, 0); + } + + /** + * Apply the callback if the value is truthy. + * + * @param bool|mixed $value + * @param callable|null $callback + * @param callable|null $default + * @return static|mixed + */ + public function when($value, callable $callback = null, callable $default = null) + { + if (! $callback) { + return new HigherOrderWhenProxy($this, $value); + } + + if ($value) { + return $callback($this, $value); + } elseif ($default) { + return $default($this, $value); + } + + return $this; + } + + /** + * Apply the callback if the collection is empty. + * + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function whenEmpty(callable $callback, callable $default = null) + { + return $this->when($this->isEmpty(), $callback, $default); + } + + /** + * Apply the callback if the collection is not empty. + * + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function whenNotEmpty(callable $callback, callable $default = null) + { + return $this->when($this->isNotEmpty(), $callback, $default); + } + + /** + * Apply the callback if the value is falsy. + * + * @param bool $value + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function unless($value, callable $callback, callable $default = null) + { + return $this->when(! $value, $callback, $default); + } + + /** + * Apply the callback unless the collection is empty. + * + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function unlessEmpty(callable $callback, callable $default = null) + { + return $this->whenNotEmpty($callback, $default); + } + + /** + * Apply the callback unless the collection is not empty. + * + * @param callable $callback + * @param callable|null $default + * @return static|mixed + */ + public function unlessNotEmpty(callable $callback, callable $default = null) + { + return $this->whenEmpty($callback, $default); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return static + */ + public function where($key, $operator = null, $value = null) + { + return $this->filter($this->operatorForWhere(...func_get_args())); + } + + /** + * Filter items where the value for the given key is null. + * + * @param string|null $key + * @return static + */ + public function whereNull($key = null) + { + return $this->whereStrict($key, null); + } + + /** + * Filter items where the value for the given key is not null. + * + * @param string|null $key + * @return static + */ + public function whereNotNull($key = null) + { + return $this->where($key, '!==', null); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $value + * @return static + */ + public function whereStrict($key, $value) + { + return $this->where($key, '===', $value); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $values + * @param bool $strict + * @return static + */ + public function whereIn($key, $values, $strict = false) + { + $values = $this->getArrayableItems($values); + + return $this->filter(function ($item) use ($key, $values, $strict) { + return in_array(data_get($item, $key), $values, $strict); + }); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $values + * @return static + */ + public function whereInStrict($key, $values) + { + return $this->whereIn($key, $values, true); + } + + /** + * Filter items such that the value of the given key is between the given values. + * + * @param string $key + * @param array $values + * @return static + */ + public function whereBetween($key, $values) + { + return $this->where($key, '>=', reset($values))->where($key, '<=', end($values)); + } + + /** + * Filter items such that the value of the given key is not between the given values. + * + * @param string $key + * @param array $values + * @return static + */ + public function whereNotBetween($key, $values) + { + return $this->filter(function ($item) use ($key, $values) { + return data_get($item, $key) < reset($values) || data_get($item, $key) > end($values); + }); + } + + /** + * Filter items by the given key value pair. + * + * @param string $key + * @param mixed $values + * @param bool $strict + * @return static + */ + public function whereNotIn($key, $values, $strict = false) + { + $values = $this->getArrayableItems($values); + + return $this->reject(function ($item) use ($key, $values, $strict) { + return in_array(data_get($item, $key), $values, $strict); + }); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * @param string $key + * @param mixed $values + * @return static + */ + public function whereNotInStrict($key, $values) + { + return $this->whereNotIn($key, $values, true); + } + + /** + * Filter the items, removing any items that don't match the given type(s). + * + * @param string|string[] $type + * @return static + */ + public function whereInstanceOf($type) + { + return $this->filter(function ($value) use ($type) { + if (is_array($type)) { + foreach ($type as $classType) { + if ($value instanceof $classType) { + return true; + } + } + + return false; + } + + return $value instanceof $type; + }); + } + + /** + * Pass the collection to the given callback and return the result. + * + * @param callable $callback + * @return mixed + */ + public function pipe(callable $callback) + { + return $callback($this); + } + + /** + * Pass the collection into a new class. + * + * @param string $class + * @return mixed + */ + public function pipeInto($class) + { + return new $class($this); + } + + /** + * Pass the collection through a series of callable pipes and return the result. + * + * @param array $pipes + * @return mixed + */ + public function pipeThrough($pipes) + { + return static::make($pipes)->reduce( + function ($carry, $pipe) { + return $pipe($carry); + }, + $this, + ); + } + + /** + * Pass the collection to the given callback and then return it. + * + * @param callable $callback + * @return $this + */ + public function tap(callable $callback) + { + $callback(clone $this); + + return $this; + } + + /** + * Reduce the collection to a single value. + * + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + $result = $initial; + + foreach ($this as $key => $value) { + $result = $callback($result, $value, $key); + } + + return $result; + } + + /** + * Reduce the collection to multiple aggregate values. + * + * @param callable $callback + * @param mixed ...$initial + * @return array + * + * @deprecated Use "reduceSpread" instead + * + * @throws \UnexpectedValueException + */ + public function reduceMany(callable $callback, ...$initial) + { + return $this->reduceSpread($callback, ...$initial); + } + + /** + * Reduce the collection to multiple aggregate values. + * + * @param callable $callback + * @param mixed ...$initial + * @return array + * + * @throws \UnexpectedValueException + */ + public function reduceSpread(callable $callback, ...$initial) + { + $result = $initial; + + foreach ($this as $key => $value) { + $result = call_user_func_array($callback, array_merge($result, [$value, $key])); + + if (! is_array($result)) { + throw new UnexpectedValueException(sprintf( + "%s::reduceMany expects reducer to return an array, but got a '%s' instead.", + class_basename(static::class), gettype($result) + )); + } + } + + return $result; + } + + /** + * Reduce an associative collection to a single value. + * + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduceWithKeys(callable $callback, $initial = null) + { + return $this->reduce($callback, $initial); + } + + /** + * Create a collection of all elements that do not pass a given truth test. + * + * @param callable|mixed $callback + * @return static + */ + public function reject($callback = true) + { + $useAsCallable = $this->useAsCallable($callback); + + return $this->filter(function ($value, $key) use ($callback, $useAsCallable) { + return $useAsCallable + ? ! $callback($value, $key) + : $value != $callback; + }); + } + + /** + * Return only unique items from the collection array using strict comparison. + * + * @param string|callable|null $key + * @return static + */ + public function uniqueStrict($key = null) + { + return $this->unique($key, true); + } + + /** + * Collect the values into a collection. + * + * @return \Tightenco\Collect\Support\Collection + */ + public function collect() + { + return new Collection($this->all()); + } + + /** + * Get the collection of items as a plain array. + * + * @return array + */ + public function toArray() + { + return $this->map(function ($value) { + return $value instanceof Arrayable ? $value->toArray() : $value; + })->all(); + } + + /** + * Convert the object into something JSON serializable. + * + * @return array + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return array_map(function ($value) { + if ($value instanceof JsonSerializable) { + return $value->jsonSerialize(); + } elseif ($value instanceof Jsonable) { + return json_decode($value->toJson(), true); + } elseif ($value instanceof Arrayable) { + return $value->toArray(); + } + + return $value; + }, $this->all()); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->jsonSerialize(), $options); + } + + /** + * Get a CachingIterator instance. + * + * @param int $flags + * @return \CachingIterator + */ + public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING) + { + return new CachingIterator($this->getIterator(), $flags); + } + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->escapeWhenCastingToString + ? e($this->toJson()) + : $this->toJson(); + } + + /** + * Indicate that the model's string representation should be escaped when __toString is invoked. + * + * @param bool $escape + * @return $this + */ + public function escapeWhenCastingToString($escape = true) + { + $this->escapeWhenCastingToString = $escape; + + return $this; + } + + /** + * Add a method to the list of proxied methods. + * + * @param string $method + * @return void + */ + public static function proxy($method) + { + static::$proxies[] = $method; + } + + /** + * Dynamically access collection proxies. + * + * @param string $key + * @return mixed + * + * @throws \Exception + */ + public function __get($key) + { + if (! in_array($key, static::$proxies)) { + throw new Exception("Property [{$key}] does not exist on this collection instance."); + } + + return new HigherOrderCollectionProxy($this, $key); + } + + /** + * Results array of items from Collection or Arrayable. + * + * @param mixed $items + * @return array + */ + protected function getArrayableItems($items) + { + if (is_array($items)) { + return $items; + } elseif ($items instanceof Enumerable) { + return $items->all(); + } elseif ($items instanceof Arrayable) { + return $items->toArray(); + } elseif ($items instanceof Jsonable) { + return json_decode($items->toJson(), true); + } elseif ($items instanceof JsonSerializable) { + return (array) $items->jsonSerialize(); + } elseif ($items instanceof Traversable) { + return iterator_to_array($items); + } elseif ($items instanceof UnitEnum) { + return [$items]; + } + + return (array) $items; + } + + /** + * Get an operator checker callback. + * + * @param string $key + * @param string|null $operator + * @param mixed $value + * @return \Closure + */ + protected function operatorForWhere($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + $value = true; + + $operator = '='; + } + + if (func_num_args() === 2) { + $value = $operator; + + $operator = '='; + } + + return function ($item) use ($key, $operator, $value) { + $retrieved = data_get($item, $key); + + $strings = array_filter([$retrieved, $value], function ($value) { + return is_string($value) || (is_object($value) && method_exists($value, '__toString')); + }); + + if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) { + return in_array($operator, ['!=', '<>', '!==']); + } + + switch ($operator) { + default: + case '=': + case '==': return $retrieved == $value; + case '!=': + case '<>': return $retrieved != $value; + case '<': return $retrieved < $value; + case '>': return $retrieved > $value; + case '<=': return $retrieved <= $value; + case '>=': return $retrieved >= $value; + case '===': return $retrieved === $value; + case '!==': return $retrieved !== $value; + } + }; + } + + /** + * Determine if the given value is callable, but not a string. + * + * @param mixed $value + * @return bool + */ + protected function useAsCallable($value) + { + return ! is_string($value) && is_callable($value); + } + + /** + * Get a value retrieving callback. + * + * @param callable|string|null $value + * @return callable + */ + protected function valueRetriever($value) + { + if ($this->useAsCallable($value)) { + return $value; + } + + return function ($item) use ($value) { + return data_get($item, $value); + }; + } + + /** + * Make a function to check an item's equality. + * + * @param mixed $value + * @return \Closure + */ + protected function equality($value) + { + return function ($item) use ($value) { + return $item === $value; + }; + } + + /** + * Make a function using another function, by negating its result. + * + * @param \Closure $callback + * @return \Closure + */ + protected function negate(Closure $callback) + { + return function (...$params) use ($callback) { + return ! $callback(...$params); + }; + } + + /** + * Make a function that returns what's passed to it. + * + * @return \Closure + */ + protected function identity() + { + return function ($value) { + return $value; + }; + } +} diff --git a/vendor/tightenco/collect/src/Collect/Support/Traits/Macroable.php b/vendor/tightenco/collect/src/Collect/Support/Traits/Macroable.php new file mode 100644 index 0000000..7e9fbd5 --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Support/Traits/Macroable.php @@ -0,0 +1,126 @@ +getMethods( + ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED + ); + + foreach ($methods as $method) { + if ($replace || ! static::hasMacro($method->name)) { + $method->setAccessible(true); + static::macro($method->name, $method->invoke($mixin)); + } + } + } + + /** + * Checks if macro is registered. + * + * @param string $name + * @return bool + */ + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + + /** + * Flush the existing macros. + * + * @return void + */ + public static function flushMacros() + { + static::$macros = []; + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public static function __callStatic($method, $parameters) + { + if (! static::hasMacro($method)) { + throw new BadMethodCallException(sprintf( + 'Method %s::%s does not exist.', static::class, $method + )); + } + + $macro = static::$macros[$method]; + + if ($macro instanceof Closure) { + $macro = $macro->bindTo(null, static::class); + } + + return $macro(...$parameters); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if (! static::hasMacro($method)) { + throw new BadMethodCallException(sprintf( + 'Method %s::%s does not exist.', static::class, $method + )); + } + + $macro = static::$macros[$method]; + + if ($macro instanceof Closure) { + $macro = $macro->bindTo($this, static::class); + } + + return $macro(...$parameters); + } +} diff --git a/vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php b/vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php new file mode 100644 index 0000000..9d75d26 --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php @@ -0,0 +1,17 @@ + Illuminate\Contracts\Support\Arrayable::class, + Tightenco\Collect\Contracts\Support\Jsonable::class => Illuminate\Contracts\Support\Jsonable::class, + Tightenco\Collect\Contracts\Support\Htmlable::class => Illuminate\Contracts\Support\Htmlable::class, + Tightenco\Collect\Contracts\Support\CanBeEscapedWhenCastToString::class => Illuminate\Contracts\Support\CanBeEscapedWhenCastToString::class, + Tightenco\Collect\Support\Arr::class => Illuminate\Support\Arr::class, + Tightenco\Collect\Support\Collection::class => Illuminate\Support\Collection::class, + Tightenco\Collect\Support\Enumerable::class => Illuminate\Support\Enumerable::class, + Tightenco\Collect\Support\HigherOrderCollectionProxy::class => Illuminate\Support\HigherOrderCollectionProxy::class, + Tightenco\Collect\Support\HigherOrderWhenProxy::class => Illuminate\Support\HigherOrderWhenProxy::class, + Tightenco\Collect\Support\LazyCollection::class => Illuminate\Support\LazyCollection::class, + Tightenco\Collect\Support\Traits\EnumeratesValues::class => Illuminate\Support\Traits\EnumeratesValues::class, +]; + +# echo "\n\n-- Aliasing....\n---------------------------------------------\n\n"; + +foreach ($aliases as $tighten => $illuminate) { + if (! class_exists($illuminate) && ! interface_exists($illuminate) && ! trait_exists($illuminate)) { + # echo "Aliasing {$tighten} to {$illuminate}.\n"; + class_alias($tighten, $illuminate); + } +} diff --git a/vendor/tightenco/collect/src/Collect/Support/helpers.php b/vendor/tightenco/collect/src/Collect/Support/helpers.php new file mode 100644 index 0000000..886a141 --- /dev/null +++ b/vendor/tightenco/collect/src/Collect/Support/helpers.php @@ -0,0 +1,122 @@ + $segment) { + unset($key[$i]); + + if (is_null($segment)) { + return $target; + } + + 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('tap')) { + /** + * Call the given Closure with the given value then return the value. + * + * @param mixed $value + * @param callable|null $callback + * @return mixed + */ + function tap($value, $callback = null) + { + if (is_null($callback)) { + return new HigherOrderTapProxy($value); + } + + $callback($value); + + return $value; + } + } + + if (! function_exists('class_basename')) { + /** + * Get the class "basename" of the given object / class. + * + * @param string|object $class + * @return string + */ + function class_basename($class) + { + $class = is_object($class) ? get_class($class) : $class; + + return basename(str_replace('\\', '/', $class)); + } + } +}