From 5057c6992e0c584f32e102bc6a360702e838d772 Mon Sep 17 00:00:00 2001 From: thiagoeramos Date: Wed, 21 Jun 2017 23:50:51 -0300 Subject: [PATCH 1/3] Aplication Thiago --- composer.json | 5 + composer.lock | 593 +++++ db/ThiagoEvangelistaRamos.sql | 46 + vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 413 +++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 9 + vendor/composer/autoload_files.php | 10 + vendor/composer/autoload_namespaces.php | 10 + vendor/composer/autoload_psr4.php | 17 + vendor/composer/autoload_real.php | 70 + vendor/composer/autoload_static.php | 84 + vendor/composer/installed.json | 594 +++++ vendor/pimple/pimple/.gitignore | 3 + vendor/pimple/pimple/.travis.yml | 32 + vendor/pimple/pimple/CHANGELOG | 35 + vendor/pimple/pimple/LICENSE | 19 + vendor/pimple/pimple/README.rst | 201 ++ vendor/pimple/pimple/composer.json | 25 + vendor/pimple/pimple/ext/pimple/.gitignore | 30 + vendor/pimple/pimple/ext/pimple/README.md | 12 + vendor/pimple/pimple/ext/pimple/config.m4 | 63 + vendor/pimple/pimple/ext/pimple/config.w32 | 13 + vendor/pimple/pimple/ext/pimple/php_pimple.h | 121 + vendor/pimple/pimple/ext/pimple/pimple.c | 922 +++++++ .../pimple/pimple/ext/pimple/pimple_compat.h | 81 + .../pimple/pimple/ext/pimple/tests/001.phpt | 45 + .../pimple/pimple/ext/pimple/tests/002.phpt | 15 + .../pimple/pimple/ext/pimple/tests/003.phpt | 16 + .../pimple/pimple/ext/pimple/tests/004.phpt | 30 + .../pimple/pimple/ext/pimple/tests/005.phpt | 27 + .../pimple/pimple/ext/pimple/tests/006.phpt | 51 + .../pimple/pimple/ext/pimple/tests/007.phpt | 22 + .../pimple/pimple/ext/pimple/tests/008.phpt | 29 + .../pimple/pimple/ext/pimple/tests/009.phpt | 13 + .../pimple/pimple/ext/pimple/tests/010.phpt | 45 + .../pimple/pimple/ext/pimple/tests/011.phpt | 19 + .../pimple/pimple/ext/pimple/tests/012.phpt | 28 + .../pimple/pimple/ext/pimple/tests/013.phpt | 33 + .../pimple/pimple/ext/pimple/tests/014.phpt | 30 + .../pimple/pimple/ext/pimple/tests/015.phpt | 17 + .../pimple/pimple/ext/pimple/tests/016.phpt | 24 + .../pimple/pimple/ext/pimple/tests/017.phpt | 17 + .../pimple/pimple/ext/pimple/tests/017_1.phpt | 17 + .../pimple/pimple/ext/pimple/tests/018.phpt | 23 + .../pimple/pimple/ext/pimple/tests/019.phpt | 18 + .../pimple/pimple/ext/pimple/tests/bench.phpb | 51 + .../pimple/ext/pimple/tests/bench_shared.phpb | 25 + vendor/pimple/pimple/phpunit.xml.dist | 14 + vendor/pimple/pimple/src/Pimple/Container.php | 282 +++ .../src/Pimple/ServiceProviderInterface.php | 46 + .../src/Pimple/Tests/Fixtures/Invokable.php | 38 + .../Pimple/Tests/Fixtures/NonInvokable.php | 34 + .../Tests/Fixtures/PimpleServiceProvider.php | 54 + .../src/Pimple/Tests/Fixtures/Service.php | 35 + .../PimpleServiceProviderInterfaceTest.php | 76 + .../pimple/src/Pimple/Tests/PimpleTest.php | 440 ++++ vendor/psr/log/.gitignore | 1 + vendor/psr/log/LICENSE | 19 + vendor/psr/log/Psr/Log/AbstractLogger.php | 128 + .../log/Psr/Log/InvalidArgumentException.php | 7 + vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + vendor/psr/log/Psr/Log/LoggerAwareTrait.php | 26 + vendor/psr/log/Psr/Log/LoggerInterface.php | 123 + vendor/psr/log/Psr/Log/LoggerTrait.php | 140 ++ vendor/psr/log/Psr/Log/NullLogger.php | 28 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 140 ++ vendor/psr/log/README.md | 45 + vendor/psr/log/composer.json | 26 + vendor/silex/silex/.gitignore | 5 + vendor/silex/silex/.travis.yml | 44 + vendor/silex/silex/LICENSE | 19 + vendor/silex/silex/README.rst | 64 + vendor/silex/silex/composer.json | 69 + vendor/silex/silex/doc/changelog.rst | 378 +++ vendor/silex/silex/doc/conf.py | 17 + vendor/silex/silex/doc/contributing.rst | 34 + .../silex/doc/cookbook/error_handler.rst | 38 + .../silex/silex/doc/cookbook/form_no_csrf.rst | 36 + .../doc/cookbook/guard_authentication.rst | 183 ++ vendor/silex/silex/doc/cookbook/index.rst | 40 + .../silex/doc/cookbook/json_request_body.rst | 95 + .../silex/doc/cookbook/multiple_loggers.rst | 69 + .../silex/doc/cookbook/session_storage.rst | 89 + .../silex/silex/doc/cookbook/sub_requests.rst | 137 + .../silex/doc/cookbook/validator_yaml.rst | 35 + vendor/silex/silex/doc/index.rst | 19 + vendor/silex/silex/doc/internals.rst | 84 + vendor/silex/silex/doc/intro.rst | 50 + vendor/silex/silex/doc/middlewares.rst | 162 ++ .../silex/doc/organizing_controllers.rst | 84 + vendor/silex/silex/doc/providers.rst | 262 ++ vendor/silex/silex/doc/providers/asset.rst | 67 + vendor/silex/silex/doc/providers/csrf.rst | 52 + vendor/silex/silex/doc/providers/doctrine.rst | 137 + vendor/silex/silex/doc/providers/form.rst | 216 ++ .../silex/silex/doc/providers/http_cache.rst | 128 + .../silex/doc/providers/http_fragment.rst | 70 + vendor/silex/silex/doc/providers/index.rst | 24 + vendor/silex/silex/doc/providers/locale.rst | 24 + vendor/silex/silex/doc/providers/monolog.rst | 115 + .../silex/silex/doc/providers/remember_me.rst | 69 + vendor/silex/silex/doc/providers/security.rst | 711 ++++++ .../silex/silex/doc/providers/serializer.rst | 73 + .../doc/providers/service_controller.rst | 142 ++ vendor/silex/silex/doc/providers/session.rst | 120 + .../silex/silex/doc/providers/swiftmailer.rst | 156 ++ .../silex/silex/doc/providers/translation.rst | 193 ++ vendor/silex/silex/doc/providers/twig.rst | 237 ++ .../silex/silex/doc/providers/validator.rst | 217 ++ .../silex/silex/doc/providers/var_dumper.rst | 44 + vendor/silex/silex/doc/services.rst | 264 ++ vendor/silex/silex/doc/testing.rst | 222 ++ vendor/silex/silex/doc/usage.rst | 799 ++++++ vendor/silex/silex/doc/web_servers.rst | 183 ++ vendor/silex/silex/phpunit.xml.dist | 24 + .../Silex/Api/BootableProviderInterface.php | 33 + .../Silex/Api/ControllerProviderInterface.php | 32 + .../Api/EventListenerProviderInterface.php | 25 + vendor/silex/silex/src/Silex/Api/LICENSE | 19 + .../silex/silex/src/Silex/Api/composer.json | 34 + .../src/Silex/AppArgumentValueResolver.php | 47 + vendor/silex/silex/src/Silex/Application.php | 506 ++++ .../silex/src/Silex/Application/FormTrait.php | 55 + .../src/Silex/Application/MonologTrait.php | 36 + .../src/Silex/Application/SecurityTrait.php | 53 + .../Silex/Application/SwiftmailerTrait.php | 33 + .../Silex/Application/TranslationTrait.php | 51 + .../silex/src/Silex/Application/TwigTrait.php | 65 + .../Silex/Application/UrlGeneratorTrait.php | 48 + .../silex/src/Silex/CallbackResolver.php | 78 + vendor/silex/silex/src/Silex/Controller.php | 122 + .../silex/src/Silex/ControllerCollection.php | 239 ++ .../silex/src/Silex/ControllerResolver.php | 54 + .../Silex/EventListener/ConverterListener.php | 66 + .../src/Silex/EventListener/LogListener.php | 134 + .../EventListener/MiddlewareListener.php | 96 + .../StringToResponseListener.php | 51 + .../Exception/ControllerFrozenException.php | 21 + .../silex/src/Silex/ExceptionHandler.php | 56 + .../src/Silex/ExceptionListenerWrapper.php | 93 + .../Silex/Provider/AssetServiceProvider.php | 83 + .../Silex/Provider/CsrfServiceProvider.php | 48 + .../Provider/DoctrineServiceProvider.php | 129 + .../ExceptionHandlerServiceProvider.php | 32 + .../Provider/Form/SilexFormExtension.php | 122 + .../Silex/Provider/FormServiceProvider.php | 89 + .../Silex/Provider/HttpCache/HttpCache.php | 39 + .../Provider/HttpCacheServiceProvider.php | 61 + .../Provider/HttpFragmentServiceProvider.php | 86 + .../Provider/HttpKernelServiceProvider.php | 101 + vendor/silex/silex/src/Silex/Provider/LICENSE | 19 + .../Silex/Provider/Locale/LocaleListener.php | 84 + .../Silex/Provider/LocaleServiceProvider.php | 40 + .../Silex/Provider/MonologServiceProvider.php | 146 ++ .../Provider/RememberMeServiceProvider.php | 107 + .../Provider/Routing/LazyRequestMatcher.php | 55 + .../Routing/RedirectableUrlMatcher.php | 54 + .../Silex/Provider/RoutingServiceProvider.php | 87 + .../Provider/SecurityServiceProvider.php | 696 ++++++ .../Provider/SerializerServiceProvider.php | 50 + .../ServiceControllerServiceProvider.php | 26 + .../Provider/Session/SessionListener.php | 39 + .../Provider/Session/TestSessionListener.php | 39 + .../Silex/Provider/SessionServiceProvider.php | 86 + .../Provider/SwiftmailerServiceProvider.php | 138 ++ .../Provider/TranslationServiceProvider.php | 98 + .../src/Silex/Provider/Twig/RuntimeLoader.php | 41 + .../Silex/Provider/TwigServiceProvider.php | 190 ++ .../Validator/ConstraintValidatorFactory.php | 63 + .../Provider/ValidatorServiceProvider.php | 62 + .../Provider/VarDumperServiceProvider.php | 58 + .../silex/src/Silex/Provider/composer.json | 31 + vendor/silex/silex/src/Silex/Route.php | 202 ++ .../silex/src/Silex/Route/SecurityTrait.php | 31 + .../src/Silex/ServiceControllerResolver.php | 60 + .../silex/src/Silex/ViewListenerWrapper.php | 87 + vendor/silex/silex/src/Silex/WebTestCase.php | 64 + .../Tests/Application/FormApplication.php | 19 + .../Silex/Tests/Application/FormTraitTest.php | 35 + .../Tests/Application/MonologApplication.php | 19 + .../Tests/Application/MonologTraitTest.php | 47 + .../Tests/Application/SecurityApplication.php | 19 + .../Tests/Application/SecurityTraitTest.php | 90 + .../Application/SwiftmailerApplication.php | 19 + .../Application/SwiftmailerTraitTest.php | 44 + .../Application/TranslationApplication.php | 19 + .../Application/TranslationTraitTest.php | 46 + .../Tests/Application/TwigApplication.php | 19 + .../Silex/Tests/Application/TwigTraitTest.php | 80 + .../Application/UrlGeneratorApplication.php | 19 + .../Application/UrlGeneratorTraitTest.php | 38 + .../tests/Silex/Tests/ApplicationTest.php | 719 ++++++ .../Silex/Tests/CallbackResolverTest.php | 81 + .../Silex/Tests/CallbackServicesTest.php | 109 + .../Silex/Tests/ControllerCollectionTest.php | 327 +++ .../Silex/Tests/ControllerResolverTest.php | 38 + .../tests/Silex/Tests/ControllerTest.php | 132 + .../Tests/EventListener/LogListenerTest.php | 93 + .../Silex/Tests/ExceptionHandlerTest.php | 406 +++ .../Silex/Tests/Fixtures/Php7Controller.php | 22 + .../tests/Silex/Tests/FunctionalTest.php | 58 + .../silex/tests/Silex/Tests/JsonTest.php | 56 + .../tests/Silex/Tests/LazyDispatcherTest.php | 59 + .../Silex/Tests/LazyRequestMatcherTest.php | 77 + .../silex/tests/Silex/Tests/LocaleTest.php | 83 + .../tests/Silex/Tests/MiddlewareTest.php | 307 +++ .../Provider/AssetServiceProviderTest.php | 35 + .../Provider/DoctrineServiceProviderTest.php | 116 + .../Provider/FormServiceProviderTest.php | 362 +++ .../DisableCsrfExtension.php | 22 + .../Provider/HttpCacheServiceProviderTest.php | 80 + .../HttpFragmentServiceProviderTest.php | 48 + .../Provider/MonologServiceProviderTest.php | 256 ++ .../RememberMeServiceProviderTest.php | 107 + .../Provider/RoutingServiceProviderTest.php | 121 + .../Provider/SecurityServiceProviderTest.php | 491 ++++ .../TokenAuthenticator.php | 79 + .../SerializerServiceProviderTest.php | 36 + .../Provider/SessionServiceProviderTest.php | 126 + .../tests/Silex/Tests/Provider/SpoolStub.php | 47 + .../SwiftmailerServiceProviderTest.php | 133 + .../TranslationServiceProviderTest.php | 181 ++ .../Provider/TwigServiceProviderTest.php | 163 ++ .../Provider/ValidatorServiceProviderTest.php | 194 ++ .../Constraint/Custom.php | 29 + .../Constraint/CustomValidator.php | 32 + .../tests/Silex/Tests/Route/SecurityRoute.php | 19 + .../Silex/Tests/Route/SecurityTraitTest.php | 85 + .../silex/tests/Silex/Tests/RouterTest.php | 285 +++ .../ServiceControllerResolverRouterTest.php | 43 + .../Tests/ServiceControllerResolverTest.php | 89 + .../silex/tests/Silex/Tests/StreamTest.php | 52 + .../tests/Silex/Tests/WebTestCaseTest.php | 78 + vendor/symfony/debug/.gitignore | 3 + vendor/symfony/debug/BufferingLogger.php | 37 + vendor/symfony/debug/CHANGELOG.md | 59 + vendor/symfony/debug/Debug.php | 63 + vendor/symfony/debug/DebugClassLoader.php | 352 +++ vendor/symfony/debug/ErrorHandler.php | 690 ++++++ .../Exception/ClassNotFoundException.php | 33 + .../debug/Exception/ContextErrorException.php | 40 + .../debug/Exception/FatalErrorException.php | 82 + .../debug/Exception/FatalThrowableError.php | 44 + .../debug/Exception/FlattenException.php | 263 ++ .../debug/Exception/OutOfMemoryException.php | 21 + .../debug/Exception/SilencedErrorContext.php | 55 + .../Exception/UndefinedFunctionException.php | 33 + .../Exception/UndefinedMethodException.php | 33 + vendor/symfony/debug/ExceptionHandler.php | 414 ++++ .../ClassNotFoundFatalErrorHandler.php | 206 ++ .../FatalErrorHandlerInterface.php | 32 + .../UndefinedFunctionFatalErrorHandler.php | 84 + .../UndefinedMethodFatalErrorHandler.php | 66 + vendor/symfony/debug/LICENSE | 19 + vendor/symfony/debug/README.md | 13 + vendor/symfony/debug/Resources/ext/README.md | 134 + vendor/symfony/debug/Resources/ext/config.m4 | 63 + vendor/symfony/debug/Resources/ext/config.w32 | 13 + .../debug/Resources/ext/php_symfony_debug.h | 60 + .../debug/Resources/ext/symfony_debug.c | 283 +++ .../debug/Resources/ext/tests/001.phpt | 153 ++ .../debug/Resources/ext/tests/002.phpt | 63 + .../debug/Resources/ext/tests/002_1.phpt | 46 + .../debug/Resources/ext/tests/003.phpt | 85 + .../debug/Tests/DebugClassLoaderTest.php | 368 +++ .../symfony/debug/Tests/ErrorHandlerTest.php | 529 ++++ .../Tests/Exception/FlattenExceptionTest.php | 301 +++ .../debug/Tests/ExceptionHandlerTest.php | 133 + .../ClassNotFoundFatalErrorHandlerTest.php | 176 ++ ...UndefinedFunctionFatalErrorHandlerTest.php | 81 + .../UndefinedMethodFatalErrorHandlerTest.php | 76 + .../debug/Tests/Fixtures/ClassAlias.php | 3 + .../debug/Tests/Fixtures/DeprecatedClass.php | 12 + .../Tests/Fixtures/DeprecatedInterface.php | 12 + .../Tests/Fixtures/ExtendedFinalMethod.php | 17 + .../debug/Tests/Fixtures/FinalClass.php | 10 + .../debug/Tests/Fixtures/FinalMethod.php | 17 + .../Tests/Fixtures/NonDeprecatedInterface.php | 7 + .../debug/Tests/Fixtures/PEARClass.php | 5 + .../debug/Tests/Fixtures/ToStringThrower.php | 24 + .../debug/Tests/Fixtures/casemismatch.php | 7 + .../debug/Tests/Fixtures/notPsr0Bis.php | 7 + .../Tests/Fixtures/psr4/Psr4CaseMismatch.php | 7 + .../debug/Tests/Fixtures/reallyNotPsr0.php | 7 + .../debug/Tests/Fixtures2/RequiredTwice.php | 7 + vendor/symfony/debug/Tests/HeaderMock.php | 38 + .../debug/Tests/MockExceptionHandler.php | 24 + vendor/symfony/debug/composer.json | 40 + vendor/symfony/debug/phpunit.xml.dist | 33 + vendor/symfony/event-dispatcher/.gitignore | 3 + vendor/symfony/event-dispatcher/CHANGELOG.md | 37 + .../ContainerAwareEventDispatcher.php | 211 ++ .../Debug/TraceableEventDispatcher.php | 324 +++ .../TraceableEventDispatcherInterface.php | 34 + .../Debug/WrappedListener.php | 116 + .../RegisterListenersPass.php | 135 + vendor/symfony/event-dispatcher/Event.php | 58 + .../event-dispatcher/EventDispatcher.php | 236 ++ .../EventDispatcherInterface.php | 100 + .../EventSubscriberInterface.php | 46 + .../symfony/event-dispatcher/GenericEvent.php | 186 ++ .../ImmutableEventDispatcher.php | 101 + vendor/symfony/event-dispatcher/LICENSE | 19 + vendor/symfony/event-dispatcher/README.md | 15 + .../Tests/AbstractEventDispatcherTest.php | 442 ++++ .../ContainerAwareEventDispatcherTest.php | 210 ++ .../Debug/TraceableEventDispatcherTest.php | 242 ++ .../RegisterListenersPassTest.php | 167 ++ .../Tests/EventDispatcherTest.php | 22 + .../event-dispatcher/Tests/EventTest.php | 55 + .../Tests/GenericEventTest.php | 140 ++ .../Tests/ImmutableEventDispatcherTest.php | 106 + vendor/symfony/event-dispatcher/composer.json | 47 + .../symfony/event-dispatcher/phpunit.xml.dist | 31 + vendor/symfony/http-foundation/.gitignore | 3 + .../symfony/http-foundation/AcceptHeader.php | 172 ++ .../http-foundation/AcceptHeaderItem.php | 226 ++ .../symfony/http-foundation/ApacheRequest.php | 43 + .../http-foundation/BinaryFileResponse.php | 361 +++ vendor/symfony/http-foundation/CHANGELOG.md | 149 ++ vendor/symfony/http-foundation/Cookie.php | 287 +++ .../Exception/ConflictingHeadersException.php | 21 + .../Exception/RequestExceptionInterface.php | 21 + .../SuspiciousOperationException.php | 20 + .../ExpressionRequestMatcher.php | 47 + .../File/Exception/AccessDeniedException.php | 30 + .../File/Exception/FileException.php | 21 + .../File/Exception/FileNotFoundException.php | 30 + .../Exception/UnexpectedTypeException.php | 20 + .../File/Exception/UploadException.php | 21 + vendor/symfony/http-foundation/File/File.php | 136 + .../File/MimeType/ExtensionGuesser.php | 96 + .../MimeType/ExtensionGuesserInterface.php | 27 + .../MimeType/FileBinaryMimeTypeGuesser.php | 87 + .../File/MimeType/FileinfoMimeTypeGuesser.php | 71 + .../MimeType/MimeTypeExtensionGuesser.php | 809 ++++++ .../File/MimeType/MimeTypeGuesser.php | 144 ++ .../MimeType/MimeTypeGuesserInterface.php | 35 + .../symfony/http-foundation/File/Stream.php | 28 + .../http-foundation/File/UploadedFile.php | 293 +++ vendor/symfony/http-foundation/FileBag.php | 145 ++ vendor/symfony/http-foundation/HeaderBag.php | 325 +++ vendor/symfony/http-foundation/IpUtils.php | 136 + .../symfony/http-foundation/JsonResponse.php | 214 ++ vendor/symfony/http-foundation/LICENSE | 19 + .../symfony/http-foundation/ParameterBag.php | 238 ++ vendor/symfony/http-foundation/README.md | 14 + .../http-foundation/RedirectResponse.php | 103 + vendor/symfony/http-foundation/Request.php | 2104 ++++++++++++++++ .../http-foundation/RequestMatcher.php | 178 ++ .../RequestMatcherInterface.php | 29 + .../symfony/http-foundation/RequestStack.php | 103 + vendor/symfony/http-foundation/Response.php | 1288 ++++++++++ .../http-foundation/ResponseHeaderBag.php | 341 +++ vendor/symfony/http-foundation/ServerBag.php | 102 + .../Session/Attribute/AttributeBag.php | 157 ++ .../Attribute/AttributeBagInterface.php | 72 + .../Attribute/NamespacedAttributeBag.php | 160 ++ .../Session/Flash/AutoExpireFlashBag.php | 175 ++ .../Session/Flash/FlashBag.php | 166 ++ .../Session/Flash/FlashBagInterface.php | 95 + .../http-foundation/Session/Session.php | 261 ++ .../Session/SessionBagInterface.php | 48 + .../Session/SessionInterface.php | 182 ++ .../Handler/MemcacheSessionHandler.php | 119 + .../Handler/MemcachedSessionHandler.php | 125 + .../Storage/Handler/MongoDbSessionHandler.php | 232 ++ .../Handler/NativeFileSessionHandler.php | 58 + .../Storage/Handler/NativeSessionHandler.php | 21 + .../Storage/Handler/NullSessionHandler.php | 70 + .../Storage/Handler/PdoSessionHandler.php | 721 ++++++ .../Handler/WriteCheckSessionHandler.php | 91 + .../Session/Storage/MetadataBag.php | 170 ++ .../Storage/MockArraySessionStorage.php | 268 ++ .../Storage/MockFileSessionStorage.php | 138 ++ .../Session/Storage/NativeSessionStorage.php | 418 ++++ .../Storage/PhpBridgeSessionStorage.php | 64 + .../Session/Storage/Proxy/AbstractProxy.php | 124 + .../Session/Storage/Proxy/NativeProxy.php | 41 + .../Storage/Proxy/SessionHandlerProxy.php | 95 + .../Storage/SessionStorageInterface.php | 139 ++ .../http-foundation/StreamedResponse.php | 132 + .../Tests/AcceptHeaderItemTest.php | 113 + .../Tests/AcceptHeaderTest.php | 103 + .../Tests/ApacheRequestTest.php | 93 + .../Tests/BinaryFileResponseTest.php | 341 +++ .../http-foundation/Tests/CookieTest.php | 205 ++ .../Tests/ExpressionRequestMatcherTest.php | 69 + .../http-foundation/Tests/File/FakeFile.php | 45 + .../http-foundation/Tests/File/FileTest.php | 180 ++ .../Tests/File/Fixtures/.unknownextension | 1 + .../Tests/File/Fixtures/directory/.empty | 0 .../Tests/File/Fixtures/other-file.example | 0 .../http-foundation/Tests/File/Fixtures/test | Bin 0 -> 35 bytes .../Tests/File/Fixtures/test.gif | Bin 0 -> 35 bytes .../Tests/File/MimeType/MimeTypeTest.php | 90 + .../Tests/File/UploadedFileTest.php | 273 ++ .../http-foundation/Tests/FileBagTest.php | 149 ++ .../http-foundation/Tests/HeaderBagTest.php | 205 ++ .../http-foundation/Tests/IpUtilsTest.php | 85 + .../Tests/JsonResponseTest.php | 253 ++ .../Tests/ParameterBagTest.php | 194 ++ .../Tests/RedirectResponseTest.php | 97 + .../Tests/RequestMatcherTest.php | 151 ++ .../Tests/RequestStackTest.php | 70 + .../http-foundation/Tests/RequestTest.php | 2207 +++++++++++++++++ .../Tests/ResponseHeaderBagTest.php | 339 +++ .../http-foundation/Tests/ResponseTest.php | 981 ++++++++ .../Tests/ResponseTestCase.php | 89 + .../http-foundation/Tests/ServerBagTest.php | 170 ++ .../Session/Attribute/AttributeBagTest.php | 189 ++ .../Attribute/NamespacedAttributeBagTest.php | 185 ++ .../Session/Flash/AutoExpireFlashBagTest.php | 156 ++ .../Tests/Session/Flash/FlashBagTest.php | 135 + .../Tests/Session/SessionTest.php | 224 ++ .../Handler/MemcacheSessionHandlerTest.php | 133 + .../Handler/MemcachedSessionHandlerTest.php | 139 ++ .../Handler/MongoDbSessionHandlerTest.php | 332 +++ .../Handler/NativeFileSessionHandlerTest.php | 77 + .../Handler/NativeSessionHandlerTest.php | 34 + .../Handler/NullSessionHandlerTest.php | 59 + .../Storage/Handler/PdoSessionHandlerTest.php | 370 +++ .../Handler/WriteCheckSessionHandlerTest.php | 95 + .../Tests/Session/Storage/MetadataBagTest.php | 142 ++ .../Storage/MockArraySessionStorageTest.php | 131 + .../Storage/MockFileSessionStorageTest.php | 127 + .../Storage/NativeSessionStorageTest.php | 247 ++ .../Storage/PhpBridgeSessionStorageTest.php | 96 + .../Storage/Proxy/AbstractProxyTest.php | 145 ++ .../Session/Storage/Proxy/NativeProxyTest.php | 36 + .../Storage/Proxy/SessionHandlerProxyTest.php | 124 + .../Tests/StreamedResponseTest.php | 115 + .../Tests/schema/http-status-codes.rng | 31 + .../Tests/schema/iana-registry.rng | 198 ++ vendor/symfony/http-foundation/composer.json | 37 + .../symfony/http-foundation/phpunit.xml.dist | 31 + vendor/symfony/http-kernel/.gitignore | 5 + vendor/symfony/http-kernel/Bundle/Bundle.php | 231 ++ .../http-kernel/Bundle/BundleInterface.php | 84 + vendor/symfony/http-kernel/CHANGELOG.md | 134 + .../CacheClearer/CacheClearerInterface.php | 27 + .../CacheClearer/ChainCacheClearer.php | 55 + .../CacheClearer/Psr6CacheClearer.php | 58 + .../http-kernel/CacheWarmer/CacheWarmer.php | 32 + .../CacheWarmer/CacheWarmerAggregate.php | 74 + .../CacheWarmer/CacheWarmerInterface.php | 32 + .../CacheWarmer/WarmableInterface.php | 27 + vendor/symfony/http-kernel/Client.php | 206 ++ .../Config/EnvParametersResource.php | 99 + .../http-kernel/Config/FileLocator.php | 56 + .../Controller/ArgumentResolver.php | 94 + .../ArgumentResolver/DefaultValueResolver.php | 40 + .../RequestAttributeValueResolver.php | 40 + .../ArgumentResolver/RequestValueResolver.php | 40 + .../ArgumentResolver/ServiceValueResolver.php | 48 + .../ArgumentResolver/SessionValueResolver.php | 46 + .../VariadicValueResolver.php | 48 + .../Controller/ArgumentResolverInterface.php | 35 + .../ArgumentValueResolverInterface.php | 43 + .../ContainerControllerResolver.php | 76 + .../Controller/ControllerReference.php | 46 + .../Controller/ControllerResolver.php | 265 ++ .../ControllerResolverInterface.php | 59 + .../Controller/TraceableArgumentResolver.php | 44 + .../TraceableControllerResolver.php | 78 + .../ControllerMetadata/ArgumentMetadata.php | 115 + .../ArgumentMetadataFactory.php | 129 + .../ArgumentMetadataFactoryInterface.php | 27 + .../DataCollector/AjaxDataCollector.php | 33 + .../DataCollector/ConfigDataCollector.php | 330 +++ .../DataCollector/DataCollector.php | 103 + .../DataCollector/DataCollectorInterface.php | 39 + .../DataCollector/DumpDataCollector.php | 303 +++ .../DataCollector/EventDataCollector.php | 108 + .../DataCollector/ExceptionDataCollector.php | 104 + .../LateDataCollectorInterface.php | 25 + .../DataCollector/LoggerDataCollector.php | 235 ++ .../DataCollector/MemoryDataCollector.php | 109 + .../DataCollector/RequestDataCollector.php | 397 +++ .../DataCollector/RouterDataCollector.php | 102 + .../DataCollector/TimeDataCollector.php | 136 + .../DataCollector/Util/ValueExporter.php | 99 + .../http-kernel/Debug/FileLinkFormatter.php | 88 + .../Debug/TraceableEventDispatcher.php | 82 + .../AddAnnotatedClassesToCachePass.php | 153 ++ .../AddClassesToCachePass.php | 25 + .../ConfigurableExtension.php | 45 + .../ControllerArgumentValueResolverPass.php | 48 + .../DependencyInjection/Extension.php | 77 + .../FragmentRendererPass.php | 67 + .../LazyLoadingFragmentHandler.php | 81 + .../MergeExtensionConfigurationPass.php | 41 + ...RegisterControllerArgumentLocatorsPass.php | 158 ++ ...oveEmptyControllerArgumentLocatorsPass.php | 76 + .../Event/FilterControllerArgumentsEvent.php | 55 + .../Event/FilterControllerEvent.php | 61 + .../http-kernel/Event/FilterResponseEvent.php | 62 + .../http-kernel/Event/FinishRequestEvent.php | 21 + .../http-kernel/Event/GetResponseEvent.php | 65 + .../GetResponseForControllerResultEvent.php | 61 + .../Event/GetResponseForExceptionEvent.php | 90 + .../symfony/http-kernel/Event/KernelEvent.php | 94 + .../http-kernel/Event/PostResponseEvent.php | 46 + .../EventListener/AbstractSessionListener.php | 54 + .../AbstractTestSessionListener.php | 84 + .../AddRequestFormatsListener.php | 57 + .../EventListener/DebugHandlersListener.php | 146 ++ .../EventListener/DumpListener.php | 59 + .../EventListener/ExceptionListener.php | 116 + .../EventListener/FragmentListener.php | 103 + .../EventListener/LocaleListener.php | 85 + .../EventListener/ProfilerListener.php | 135 + .../EventListener/ResponseListener.php | 58 + .../EventListener/RouterListener.php | 140 ++ .../EventListener/SaveSessionListener.php | 66 + .../EventListener/SessionListener.php | 40 + .../StreamedResponseListener.php | 51 + .../EventListener/SurrogateListener.php | 58 + .../EventListener/TestSessionListener.php | 40 + .../EventListener/TranslatorListener.php | 69 + .../EventListener/ValidateRequestListener.php | 55 + .../Exception/AccessDeniedHttpException.php | 33 + .../Exception/BadRequestHttpException.php | 32 + .../Exception/ConflictHttpException.php | 32 + .../Exception/GoneHttpException.php | 32 + .../http-kernel/Exception/HttpException.php | 51 + .../Exception/HttpExceptionInterface.php | 34 + .../Exception/LengthRequiredHttpException.php | 32 + .../MethodNotAllowedHttpException.php | 35 + .../Exception/NotAcceptableHttpException.php | 32 + .../Exception/NotFoundHttpException.php | 32 + .../PreconditionFailedHttpException.php | 32 + .../PreconditionRequiredHttpException.php | 34 + .../ServiceUnavailableHttpException.php | 38 + .../TooManyRequestsHttpException.php | 40 + .../Exception/UnauthorizedHttpException.php | 35 + .../UnprocessableEntityHttpException.php | 32 + .../UnsupportedMediaTypeHttpException.php | 32 + .../AbstractSurrogateFragmentRenderer.php | 112 + .../Fragment/EsiFragmentRenderer.php | 28 + .../http-kernel/Fragment/FragmentHandler.php | 118 + .../Fragment/FragmentRendererInterface.php | 42 + .../Fragment/HIncludeFragmentRenderer.php | 167 ++ .../Fragment/InlineFragmentRenderer.php | 158 ++ .../Fragment/RoutableFragmentRenderer.php | 90 + .../Fragment/SsiFragmentRenderer.php | 28 + .../HttpCache/AbstractSurrogate.php | 138 ++ vendor/symfony/http-kernel/HttpCache/Esi.php | 115 + .../http-kernel/HttpCache/HttpCache.php | 737 ++++++ .../HttpCache/ResponseCacheStrategy.php | 93 + .../ResponseCacheStrategyInterface.php | 41 + vendor/symfony/http-kernel/HttpCache/Ssi.php | 98 + .../symfony/http-kernel/HttpCache/Store.php | 508 ++++ .../http-kernel/HttpCache/StoreInterface.php | 96 + .../HttpCache/SurrogateInterface.php | 103 + vendor/symfony/http-kernel/HttpKernel.php | 301 +++ .../http-kernel/HttpKernelInterface.php | 43 + vendor/symfony/http-kernel/Kernel.php | 849 +++++++ vendor/symfony/http-kernel/KernelEvents.php | 119 + .../symfony/http-kernel/KernelInterface.php | 164 ++ vendor/symfony/http-kernel/LICENSE | 19 + .../http-kernel/Log/DebugLoggerInterface.php | 38 + .../Profiler/FileProfilerStorage.php | 284 +++ .../symfony/http-kernel/Profiler/Profile.php | 295 +++ .../symfony/http-kernel/Profiler/Profiler.php | 270 ++ .../Profiler/ProfilerStorageInterface.php | 59 + vendor/symfony/http-kernel/README.md | 16 + .../http-kernel/TerminableInterface.php | 35 + .../http-kernel/Tests/Bundle/BundleTest.php | 100 + .../CacheClearer/ChainCacheClearerTest.php | 58 + .../CacheClearer/Psr6CacheClearerTest.php | 69 + .../CacheWarmer/CacheWarmerAggregateTest.php | 101 + .../Tests/CacheWarmer/CacheWarmerTest.php | 68 + .../symfony/http-kernel/Tests/ClientTest.php | 183 ++ .../Config/EnvParametersResourceTest.php | 107 + .../Tests/Config/FileLocatorTest.php | 48 + .../Tests/Controller/ArgumentResolverTest.php | 349 +++ .../ContainerControllerResolverTest.php | 148 ++ .../Controller/ControllerResolverTest.php | 331 +++ .../ArgumentMetadataFactoryTest.php | 148 ++ .../ArgumentMetadataTest.php | 46 + .../Tests/DataCollector/Compiler.log | 4 + .../DataCollector/ConfigDataCollectorTest.php | 66 + .../Tests/DataCollector/DataCollectorTest.php | 38 + .../DataCollector/DumpDataCollectorTest.php | 115 + .../ExceptionDataCollectorTest.php | 40 + .../DataCollector/LoggerDataCollectorTest.php | 126 + .../DataCollector/MemoryDataCollectorTest.php | 59 + .../RequestDataCollectorTest.php | 287 +++ .../DataCollector/TimeDataCollectorTest.php | 55 + .../DataCollector/Util/ValueExporterTest.php | 51 + .../Tests/Debug/FileLinkFormatterTest.php | 67 + .../Debug/TraceableEventDispatcherTest.php | 121 + .../AddAnnotatedClassesToCachePassTest.php | 99 + ...ontrollerArgumentValueResolverPassTest.php | 67 + .../FragmentRendererPassTest.php | 105 + .../LazyLoadingFragmentHandlerTest.php | 66 + .../MergeExtensionConfigurationPassTest.php | 55 + ...sterControllerArgumentLocatorsPassTest.php | 329 +++ ...mptyControllerArgumentLocatorsPassTest.php | 148 ++ .../GetResponseForExceptionEventTest.php | 27 + .../AddRequestFormatsListenerTest.php | 84 + .../DebugHandlersListenerTest.php | 135 + .../Tests/EventListener/DumpListenerTest.php | 81 + .../EventListener/ExceptionListenerTest.php | 149 ++ .../EventListener/FragmentListenerTest.php | 122 + .../EventListener/LocaleListenerTest.php | 103 + .../EventListener/ProfilerListenerTest.php | 71 + .../EventListener/ResponseListenerTest.php | 95 + .../EventListener/RouterListenerTest.php | 188 ++ .../EventListener/SurrogateListenerTest.php | 67 + .../EventListener/TestSessionListenerTest.php | 145 ++ .../EventListener/TranslatorListenerTest.php | 118 + .../ValidateRequestListenerTest.php | 43 + .../AccessDeniedHttpExceptionTest.php | 13 + .../Exception/BadRequestHttpExceptionTest.php | 13 + .../Exception/ConflictHttpExceptionTest.php | 13 + .../Tests/Exception/GoneHttpExceptionTest.php | 13 + .../Tests/Exception/HttpExceptionTest.php | 53 + .../LengthRequiredHttpExceptionTest.php | 13 + .../MethodNotAllowedHttpExceptionTest.php | 24 + .../NotAcceptableHttpExceptionTest.php | 13 + .../Exception/NotFoundHttpExceptionTest.php | 13 + .../PreconditionFailedHttpExceptionTest.php | 13 + .../PreconditionRequiredHttpExceptionTest.php | 13 + .../ServiceUnavailableHttpExceptionTest.php | 29 + .../TooManyRequestsHttpExceptionTest.php | 29 + .../UnauthorizedHttpExceptionTest.php | 24 + .../UnprocessableEntityHttpExceptionTest.php | 28 + .../UnsupportedMediaTypeHttpExceptionTest.php | 23 + .../Tests/Fixtures/123/Kernel123.php | 37 + .../Fixtures/BaseBundle/Resources/foo.txt | 0 .../Fixtures/BaseBundle/Resources/hide.txt | 0 .../Fixtures/Bundle1Bundle/Resources/foo.txt | 0 .../Tests/Fixtures/Bundle1Bundle/bar.txt | 0 .../Tests/Fixtures/Bundle1Bundle/foo.txt | 0 .../Tests/Fixtures/Bundle2Bundle/foo.txt | 0 .../Fixtures/ChildBundle/Resources/foo.txt | 0 .../Fixtures/ChildBundle/Resources/hide.txt | 0 .../Controller/BasicTypesController.php | 19 + .../Fixtures/Controller/ExtendingRequest.php | 18 + .../Fixtures/Controller/ExtendingSession.php | 18 + .../Controller/NullableController.php | 19 + .../Controller/VariadicController.php | 19 + .../DataCollector/CloneVarDataCollector.php | 41 + .../ExtensionAbsentBundle.php | 18 + .../ExtensionLoadedExtension.php | 22 + .../ExtensionLoadedBundle.php | 18 + .../ExtensionNotValidExtension.php | 20 + .../ExtensionNotValidBundle.php | 18 + .../Command/BarCommand.php | 17 + .../Command/FooCommand.php | 22 + .../ExtensionPresentExtension.php | 22 + .../ExtensionPresentBundle.php | 18 + .../Tests/Fixtures/KernelForOverrideName.php | 28 + .../Tests/Fixtures/KernelForTest.php | 37 + .../Tests/Fixtures/KernelWithoutBundles.php | 33 + .../Fixtures/Resources/BaseBundle/hide.txt | 0 .../Fixtures/Resources/Bundle1Bundle/foo.txt | 0 .../Fixtures/Resources/ChildBundle/foo.txt | 0 .../Fixtures/Resources/FooBundle/foo.txt | 0 .../http-kernel/Tests/Fixtures/TestClient.php | 31 + .../Tests/Fixtures/TestEventDispatcher.php | 28 + .../Fragment/EsiFragmentRendererTest.php | 110 + .../Tests/Fragment/FragmentHandlerTest.php | 99 + .../Fragment/HIncludeFragmentRendererTest.php | 89 + .../Fragment/InlineFragmentRendererTest.php | 250 ++ .../Fragment/RoutableFragmentRendererTest.php | 94 + .../Fragment/SsiFragmentRendererTest.php | 97 + .../http-kernel/Tests/HttpCache/EsiTest.php | 248 ++ .../Tests/HttpCache/HttpCacheTest.php | 1367 ++++++++++ .../Tests/HttpCache/HttpCacheTestCase.php | 185 ++ .../HttpCache/ResponseCacheStrategyTest.php | 78 + .../http-kernel/Tests/HttpCache/SsiTest.php | 215 ++ .../http-kernel/Tests/HttpCache/StoreTest.php | 301 +++ .../Tests/HttpCache/TestHttpKernel.php | 92 + .../HttpCache/TestMultipleHttpKernel.php | 81 + .../http-kernel/Tests/HttpKernelTest.php | 403 +++ .../symfony/http-kernel/Tests/KernelTest.php | 900 +++++++ vendor/symfony/http-kernel/Tests/Logger.php | 88 + .../Profiler/FileProfilerStorageTest.php | 350 +++ .../Tests/Profiler/ProfilerTest.php | 91 + .../http-kernel/Tests/TestHttpKernel.php | 42 + .../http-kernel/Tests/UriSignerTest.php | 64 + vendor/symfony/http-kernel/UriSigner.php | 112 + vendor/symfony/http-kernel/composer.json | 70 + vendor/symfony/http-kernel/phpunit.xml.dist | 40 + vendor/symfony/polyfill-mbstring/LICENSE | 19 + vendor/symfony/polyfill-mbstring/Mbstring.php | 664 +++++ vendor/symfony/polyfill-mbstring/README.md | 13 + .../Resources/unidata/lowerCase.php | 1101 ++++++++ .../Resources/unidata/upperCase.php | 1109 +++++++++ .../symfony/polyfill-mbstring/bootstrap.php | 56 + .../symfony/polyfill-mbstring/composer.json | 34 + vendor/symfony/routing/.gitignore | 3 + vendor/symfony/routing/Annotation/Route.php | 146 ++ vendor/symfony/routing/CHANGELOG.md | 219 ++ vendor/symfony/routing/CompiledRoute.php | 171 ++ .../RoutingResolverPass.php | 46 + .../routing/Exception/ExceptionInterface.php | 21 + .../Exception/InvalidParameterException.php | 21 + .../Exception/MethodNotAllowedException.php | 44 + .../MissingMandatoryParametersException.php | 22 + .../Exception/ResourceNotFoundException.php | 23 + .../Exception/RouteNotFoundException.php | 21 + .../ConfigurableRequirementsInterface.php | 55 + .../Generator/Dumper/GeneratorDumper.php | 45 + .../Dumper/GeneratorDumperInterface.php | 39 + .../Generator/Dumper/PhpGeneratorDumper.php | 123 + .../routing/Generator/UrlGenerator.php | 339 +++ .../Generator/UrlGeneratorInterface.php | 86 + vendor/symfony/routing/LICENSE | 19 + .../routing/Loader/AnnotationClassLoader.php | 270 ++ .../Loader/AnnotationDirectoryLoader.php | 89 + .../routing/Loader/AnnotationFileLoader.php | 147 ++ .../symfony/routing/Loader/ClosureLoader.php | 46 + .../ServiceRouterLoader.php | 40 + .../routing/Loader/DirectoryLoader.php | 58 + .../routing/Loader/ObjectRouteLoader.php | 95 + .../symfony/routing/Loader/PhpFileLoader.php | 66 + .../symfony/routing/Loader/XmlFileLoader.php | 342 +++ .../symfony/routing/Loader/YamlFileLoader.php | 208 ++ .../Loader/schema/routing/routing-1.0.xsd | 146 ++ .../Matcher/Dumper/DumperCollection.php | 161 ++ .../routing/Matcher/Dumper/DumperRoute.php | 66 + .../routing/Matcher/Dumper/MatcherDumper.php | 45 + .../Matcher/Dumper/MatcherDumperInterface.php | 39 + .../Matcher/Dumper/PhpMatcherDumper.php | 431 ++++ .../Matcher/Dumper/StaticPrefixCollection.php | 238 ++ .../Matcher/RedirectableUrlMatcher.php | 65 + .../RedirectableUrlMatcherInterface.php | 31 + .../Matcher/RequestMatcherInterface.php | 39 + .../routing/Matcher/TraceableUrlMatcher.php | 141 ++ vendor/symfony/routing/Matcher/UrlMatcher.php | 251 ++ .../routing/Matcher/UrlMatcherInterface.php | 39 + vendor/symfony/routing/README.md | 13 + vendor/symfony/routing/RequestContext.php | 344 +++ .../routing/RequestContextAwareInterface.php | 29 + vendor/symfony/routing/Route.php | 593 +++++ vendor/symfony/routing/RouteCollection.php | 277 +++ .../routing/RouteCollectionBuilder.php | 383 +++ vendor/symfony/routing/RouteCompiler.php | 319 +++ .../routing/RouteCompilerInterface.php | 32 + vendor/symfony/routing/Router.php | 388 +++ vendor/symfony/routing/RouterInterface.php | 32 + .../routing/Tests/Annotation/RouteTest.php | 50 + .../routing/Tests/CompiledRouteTest.php | 27 + .../RoutingResolverPassTest.php | 36 + .../AnnotatedClasses/AbstractClass.php | 16 + .../Fixtures/AnnotatedClasses/BarClass.php | 19 + .../Fixtures/AnnotatedClasses/BazClass.php | 19 + .../Fixtures/AnnotatedClasses/FooClass.php | 16 + .../Fixtures/AnnotatedClasses/FooTrait.php | 13 + .../Tests/Fixtures/CustomXmlFileLoader.php | 26 + .../OtherAnnotatedClasses/NoStartTagClass.php | 3 + .../OtherAnnotatedClasses/VariadicClass.php | 19 + .../Tests/Fixtures/RedirectableUrlMatcher.php | 30 + .../routing/Tests/Fixtures/annotated.php | 0 .../routing/Tests/Fixtures/bad_format.yml | 3 + vendor/symfony/routing/Tests/Fixtures/bar.xml | 0 .../Fixtures/directory/recurse/routes1.yml | 2 + .../Fixtures/directory/recurse/routes2.yml | 2 + .../Tests/Fixtures/directory/routes3.yml | 2 + .../Fixtures/directory_import/import.yml | 3 + .../Tests/Fixtures/dumper/url_matcher1.apache | 163 ++ .../Tests/Fixtures/dumper/url_matcher1.php | 317 +++ .../Tests/Fixtures/dumper/url_matcher2.apache | 7 + .../Tests/Fixtures/dumper/url_matcher2.php | 349 +++ .../Tests/Fixtures/dumper/url_matcher3.php | 58 + .../Tests/Fixtures/dumper/url_matcher4.php | 98 + .../Tests/Fixtures/dumper/url_matcher5.php | 158 ++ .../Tests/Fixtures/dumper/url_matcher6.php | 204 ++ .../Tests/Fixtures/dumper/url_matcher7.php | 228 ++ .../symfony/routing/Tests/Fixtures/empty.yml | 0 .../routing/Tests/Fixtures/file_resource.yml | 0 vendor/symfony/routing/Tests/Fixtures/foo.xml | 0 .../symfony/routing/Tests/Fixtures/foo1.xml | 0 .../routing/Tests/Fixtures/incomplete.yml | 2 + .../routing/Tests/Fixtures/list_defaults.xml | 20 + .../Tests/Fixtures/list_in_list_defaults.xml | 22 + .../Tests/Fixtures/list_in_map_defaults.xml | 22 + .../Tests/Fixtures/list_null_values.xml | 22 + .../routing/Tests/Fixtures/map_defaults.xml | 20 + .../Tests/Fixtures/map_in_list_defaults.xml | 22 + .../Tests/Fixtures/map_in_map_defaults.xml | 22 + .../Tests/Fixtures/map_null_values.xml | 22 + .../routing/Tests/Fixtures/missing_id.xml | 8 + .../routing/Tests/Fixtures/missing_path.xml | 8 + .../Tests/Fixtures/namespaceprefix.xml | 16 + .../Fixtures/nonesense_resource_plus_path.yml | 3 + .../nonesense_type_without_resource.yml | 3 + .../routing/Tests/Fixtures/nonvalid.xml | 10 + .../routing/Tests/Fixtures/nonvalid.yml | 1 + .../routing/Tests/Fixtures/nonvalid2.yml | 1 + .../routing/Tests/Fixtures/nonvalidkeys.yml | 3 + .../routing/Tests/Fixtures/nonvalidnode.xml | 8 + .../routing/Tests/Fixtures/nonvalidroute.xml | 12 + .../routing/Tests/Fixtures/null_values.xml | 12 + .../Tests/Fixtures/scalar_defaults.xml | 33 + .../Tests/Fixtures/special_route_name.yml | 2 + .../routing/Tests/Fixtures/validpattern.php | 18 + .../routing/Tests/Fixtures/validpattern.xml | 15 + .../routing/Tests/Fixtures/validpattern.yml | 13 + .../routing/Tests/Fixtures/validresource.php | 18 + .../routing/Tests/Fixtures/validresource.xml | 13 + .../routing/Tests/Fixtures/validresource.yml | 8 + .../Fixtures/with_define_path_variable.php | 5 + .../routing/Tests/Fixtures/withdoctype.xml | 3 + .../Dumper/PhpGeneratorDumperTest.php | 181 ++ .../Tests/Generator/UrlGeneratorTest.php | 692 ++++++ .../Loader/AbstractAnnotationLoaderTest.php | 33 + .../Loader/AnnotationClassLoaderTest.php | 254 ++ .../Loader/AnnotationDirectoryLoaderTest.php | 80 + .../Tests/Loader/AnnotationFileLoaderTest.php | 80 + .../Tests/Loader/ClosureLoaderTest.php | 49 + .../Tests/Loader/DirectoryLoaderTest.php | 74 + .../Tests/Loader/ObjectRouteLoaderTest.php | 123 + .../Tests/Loader/PhpFileLoaderTest.php | 83 + .../Tests/Loader/XmlFileLoaderTest.php | 290 +++ .../Tests/Loader/YamlFileLoaderTest.php | 111 + .../Matcher/Dumper/DumperCollectionTest.php | 34 + .../Matcher/Dumper/PhpMatcherDumperTest.php | 377 +++ .../Dumper/StaticPrefixCollectionTest.php | 175 ++ .../Matcher/RedirectableUrlMatcherTest.php | 72 + .../Tests/Matcher/TraceableUrlMatcherTest.php | 122 + .../routing/Tests/Matcher/UrlMatcherTest.php | 420 ++++ .../routing/Tests/RequestContextTest.php | 160 ++ .../Tests/RouteCollectionBuilderTest.php | 325 +++ .../routing/Tests/RouteCollectionTest.php | 305 +++ .../routing/Tests/RouteCompilerTest.php | 389 +++ vendor/symfony/routing/Tests/RouteTest.php | 240 ++ vendor/symfony/routing/Tests/RouterTest.php | 162 ++ vendor/symfony/routing/composer.json | 56 + vendor/symfony/routing/phpunit.xml.dist | 30 + web/.htaccess | 10 + web/index.php | 117 + 838 files changed, 97243 insertions(+) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 db/ThiagoEvangelistaRamos.sql create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_files.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/pimple/pimple/.gitignore create mode 100644 vendor/pimple/pimple/.travis.yml create mode 100644 vendor/pimple/pimple/CHANGELOG create mode 100644 vendor/pimple/pimple/LICENSE create mode 100644 vendor/pimple/pimple/README.rst create mode 100644 vendor/pimple/pimple/composer.json create mode 100644 vendor/pimple/pimple/ext/pimple/.gitignore create mode 100644 vendor/pimple/pimple/ext/pimple/README.md create mode 100644 vendor/pimple/pimple/ext/pimple/config.m4 create mode 100644 vendor/pimple/pimple/ext/pimple/config.w32 create mode 100644 vendor/pimple/pimple/ext/pimple/php_pimple.h create mode 100644 vendor/pimple/pimple/ext/pimple/pimple.c create mode 100644 vendor/pimple/pimple/ext/pimple/pimple_compat.h create mode 100644 vendor/pimple/pimple/ext/pimple/tests/001.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/002.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/003.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/004.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/005.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/006.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/007.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/008.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/009.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/010.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/011.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/012.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/013.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/014.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/015.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/016.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/017.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/017_1.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/018.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/019.phpt create mode 100644 vendor/pimple/pimple/ext/pimple/tests/bench.phpb create mode 100644 vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb create mode 100644 vendor/pimple/pimple/phpunit.xml.dist create mode 100644 vendor/pimple/pimple/src/Pimple/Container.php create mode 100644 vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Invokable.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/PimpleServiceProvider.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php create mode 100644 vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php create mode 100644 vendor/psr/log/.gitignore create mode 100644 vendor/psr/log/LICENSE create mode 100644 vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 vendor/psr/log/README.md create mode 100644 vendor/psr/log/composer.json create mode 100644 vendor/silex/silex/.gitignore create mode 100644 vendor/silex/silex/.travis.yml create mode 100644 vendor/silex/silex/LICENSE create mode 100644 vendor/silex/silex/README.rst create mode 100644 vendor/silex/silex/composer.json create mode 100644 vendor/silex/silex/doc/changelog.rst create mode 100644 vendor/silex/silex/doc/conf.py create mode 100644 vendor/silex/silex/doc/contributing.rst create mode 100644 vendor/silex/silex/doc/cookbook/error_handler.rst create mode 100644 vendor/silex/silex/doc/cookbook/form_no_csrf.rst create mode 100644 vendor/silex/silex/doc/cookbook/guard_authentication.rst create mode 100644 vendor/silex/silex/doc/cookbook/index.rst create mode 100644 vendor/silex/silex/doc/cookbook/json_request_body.rst create mode 100644 vendor/silex/silex/doc/cookbook/multiple_loggers.rst create mode 100644 vendor/silex/silex/doc/cookbook/session_storage.rst create mode 100644 vendor/silex/silex/doc/cookbook/sub_requests.rst create mode 100644 vendor/silex/silex/doc/cookbook/validator_yaml.rst create mode 100644 vendor/silex/silex/doc/index.rst create mode 100644 vendor/silex/silex/doc/internals.rst create mode 100644 vendor/silex/silex/doc/intro.rst create mode 100644 vendor/silex/silex/doc/middlewares.rst create mode 100644 vendor/silex/silex/doc/organizing_controllers.rst create mode 100644 vendor/silex/silex/doc/providers.rst create mode 100644 vendor/silex/silex/doc/providers/asset.rst create mode 100644 vendor/silex/silex/doc/providers/csrf.rst create mode 100644 vendor/silex/silex/doc/providers/doctrine.rst create mode 100644 vendor/silex/silex/doc/providers/form.rst create mode 100644 vendor/silex/silex/doc/providers/http_cache.rst create mode 100644 vendor/silex/silex/doc/providers/http_fragment.rst create mode 100644 vendor/silex/silex/doc/providers/index.rst create mode 100644 vendor/silex/silex/doc/providers/locale.rst create mode 100644 vendor/silex/silex/doc/providers/monolog.rst create mode 100644 vendor/silex/silex/doc/providers/remember_me.rst create mode 100644 vendor/silex/silex/doc/providers/security.rst create mode 100644 vendor/silex/silex/doc/providers/serializer.rst create mode 100644 vendor/silex/silex/doc/providers/service_controller.rst create mode 100644 vendor/silex/silex/doc/providers/session.rst create mode 100644 vendor/silex/silex/doc/providers/swiftmailer.rst create mode 100644 vendor/silex/silex/doc/providers/translation.rst create mode 100644 vendor/silex/silex/doc/providers/twig.rst create mode 100644 vendor/silex/silex/doc/providers/validator.rst create mode 100644 vendor/silex/silex/doc/providers/var_dumper.rst create mode 100644 vendor/silex/silex/doc/services.rst create mode 100644 vendor/silex/silex/doc/testing.rst create mode 100644 vendor/silex/silex/doc/usage.rst create mode 100644 vendor/silex/silex/doc/web_servers.rst create mode 100644 vendor/silex/silex/phpunit.xml.dist create mode 100644 vendor/silex/silex/src/Silex/Api/BootableProviderInterface.php create mode 100644 vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.php create mode 100644 vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php create mode 100644 vendor/silex/silex/src/Silex/Api/LICENSE create mode 100644 vendor/silex/silex/src/Silex/Api/composer.json create mode 100644 vendor/silex/silex/src/Silex/AppArgumentValueResolver.php create mode 100644 vendor/silex/silex/src/Silex/Application.php create mode 100644 vendor/silex/silex/src/Silex/Application/FormTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/MonologTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/SecurityTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/TranslationTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/TwigTrait.php create mode 100644 vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php create mode 100644 vendor/silex/silex/src/Silex/CallbackResolver.php create mode 100644 vendor/silex/silex/src/Silex/Controller.php create mode 100644 vendor/silex/silex/src/Silex/ControllerCollection.php create mode 100644 vendor/silex/silex/src/Silex/ControllerResolver.php create mode 100644 vendor/silex/silex/src/Silex/EventListener/ConverterListener.php create mode 100644 vendor/silex/silex/src/Silex/EventListener/LogListener.php create mode 100644 vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php create mode 100644 vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.php create mode 100644 vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php create mode 100644 vendor/silex/silex/src/Silex/ExceptionHandler.php create mode 100644 vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php create mode 100644 vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php create mode 100644 vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php create mode 100644 vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/LICENSE create mode 100644 vendor/silex/silex/src/Silex/Provider/Locale/LocaleListener.php create mode 100644 vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php create mode 100644 vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php create mode 100644 vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php create mode 100644 vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.php create mode 100644 vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/composer.json create mode 100644 vendor/silex/silex/src/Silex/Route.php create mode 100644 vendor/silex/silex/src/Silex/Route/SecurityTrait.php create mode 100644 vendor/silex/silex/src/Silex/ServiceControllerResolver.php create mode 100644 vendor/silex/silex/src/Silex/ViewListenerWrapper.php create mode 100644 vendor/silex/silex/src/Silex/WebTestCase.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ControllerTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/JsonTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/LocaleTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/RouterTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/StreamTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php create mode 100644 vendor/symfony/debug/.gitignore create mode 100644 vendor/symfony/debug/BufferingLogger.php create mode 100644 vendor/symfony/debug/CHANGELOG.md create mode 100644 vendor/symfony/debug/Debug.php create mode 100644 vendor/symfony/debug/DebugClassLoader.php create mode 100644 vendor/symfony/debug/ErrorHandler.php create mode 100644 vendor/symfony/debug/Exception/ClassNotFoundException.php create mode 100644 vendor/symfony/debug/Exception/ContextErrorException.php create mode 100644 vendor/symfony/debug/Exception/FatalErrorException.php create mode 100644 vendor/symfony/debug/Exception/FatalThrowableError.php create mode 100644 vendor/symfony/debug/Exception/FlattenException.php create mode 100644 vendor/symfony/debug/Exception/OutOfMemoryException.php create mode 100644 vendor/symfony/debug/Exception/SilencedErrorContext.php create mode 100644 vendor/symfony/debug/Exception/UndefinedFunctionException.php create mode 100644 vendor/symfony/debug/Exception/UndefinedMethodException.php create mode 100644 vendor/symfony/debug/ExceptionHandler.php create mode 100644 vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php create mode 100644 vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php create mode 100644 vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php create mode 100644 vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php create mode 100644 vendor/symfony/debug/LICENSE create mode 100644 vendor/symfony/debug/README.md create mode 100644 vendor/symfony/debug/Resources/ext/README.md create mode 100644 vendor/symfony/debug/Resources/ext/config.m4 create mode 100644 vendor/symfony/debug/Resources/ext/config.w32 create mode 100644 vendor/symfony/debug/Resources/ext/php_symfony_debug.h create mode 100644 vendor/symfony/debug/Resources/ext/symfony_debug.c create mode 100644 vendor/symfony/debug/Resources/ext/tests/001.phpt create mode 100644 vendor/symfony/debug/Resources/ext/tests/002.phpt create mode 100644 vendor/symfony/debug/Resources/ext/tests/002_1.phpt create mode 100644 vendor/symfony/debug/Resources/ext/tests/003.phpt create mode 100644 vendor/symfony/debug/Tests/DebugClassLoaderTest.php create mode 100644 vendor/symfony/debug/Tests/ErrorHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php create mode 100644 vendor/symfony/debug/Tests/ExceptionHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/ClassAlias.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/DeprecatedClass.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/DeprecatedInterface.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/ExtendedFinalMethod.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/FinalClass.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/FinalMethod.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/NonDeprecatedInterface.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/PEARClass.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/ToStringThrower.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/casemismatch.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/notPsr0Bis.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/psr4/Psr4CaseMismatch.php create mode 100644 vendor/symfony/debug/Tests/Fixtures/reallyNotPsr0.php create mode 100644 vendor/symfony/debug/Tests/Fixtures2/RequiredTwice.php create mode 100644 vendor/symfony/debug/Tests/HeaderMock.php create mode 100644 vendor/symfony/debug/Tests/MockExceptionHandler.php create mode 100644 vendor/symfony/debug/composer.json create mode 100644 vendor/symfony/debug/phpunit.xml.dist create mode 100644 vendor/symfony/event-dispatcher/.gitignore create mode 100644 vendor/symfony/event-dispatcher/CHANGELOG.md create mode 100644 vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php create mode 100644 vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php create mode 100644 vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php create mode 100644 vendor/symfony/event-dispatcher/Debug/WrappedListener.php create mode 100644 vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php create mode 100644 vendor/symfony/event-dispatcher/Event.php create mode 100644 vendor/symfony/event-dispatcher/EventDispatcher.php create mode 100644 vendor/symfony/event-dispatcher/EventDispatcherInterface.php create mode 100644 vendor/symfony/event-dispatcher/EventSubscriberInterface.php create mode 100644 vendor/symfony/event-dispatcher/GenericEvent.php create mode 100644 vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php create mode 100644 vendor/symfony/event-dispatcher/LICENSE create mode 100644 vendor/symfony/event-dispatcher/README.md create mode 100644 vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php create mode 100644 vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php create mode 100644 vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php create mode 100644 vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php create mode 100644 vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php create mode 100644 vendor/symfony/event-dispatcher/Tests/EventTest.php create mode 100644 vendor/symfony/event-dispatcher/Tests/GenericEventTest.php create mode 100644 vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php create mode 100644 vendor/symfony/event-dispatcher/composer.json create mode 100644 vendor/symfony/event-dispatcher/phpunit.xml.dist create mode 100644 vendor/symfony/http-foundation/.gitignore create mode 100644 vendor/symfony/http-foundation/AcceptHeader.php create mode 100644 vendor/symfony/http-foundation/AcceptHeaderItem.php create mode 100644 vendor/symfony/http-foundation/ApacheRequest.php create mode 100644 vendor/symfony/http-foundation/BinaryFileResponse.php create mode 100644 vendor/symfony/http-foundation/CHANGELOG.md create mode 100644 vendor/symfony/http-foundation/Cookie.php create mode 100644 vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php create mode 100644 vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php create mode 100644 vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php create mode 100644 vendor/symfony/http-foundation/ExpressionRequestMatcher.php create mode 100644 vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/FileException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php create mode 100644 vendor/symfony/http-foundation/File/Exception/UploadException.php create mode 100644 vendor/symfony/http-foundation/File/File.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php create mode 100644 vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php create mode 100644 vendor/symfony/http-foundation/File/Stream.php create mode 100644 vendor/symfony/http-foundation/File/UploadedFile.php create mode 100644 vendor/symfony/http-foundation/FileBag.php create mode 100644 vendor/symfony/http-foundation/HeaderBag.php create mode 100644 vendor/symfony/http-foundation/IpUtils.php create mode 100644 vendor/symfony/http-foundation/JsonResponse.php create mode 100644 vendor/symfony/http-foundation/LICENSE create mode 100644 vendor/symfony/http-foundation/ParameterBag.php create mode 100644 vendor/symfony/http-foundation/README.md create mode 100644 vendor/symfony/http-foundation/RedirectResponse.php create mode 100644 vendor/symfony/http-foundation/Request.php create mode 100644 vendor/symfony/http-foundation/RequestMatcher.php create mode 100644 vendor/symfony/http-foundation/RequestMatcherInterface.php create mode 100644 vendor/symfony/http-foundation/RequestStack.php create mode 100644 vendor/symfony/http-foundation/Response.php create mode 100644 vendor/symfony/http-foundation/ResponseHeaderBag.php create mode 100644 vendor/symfony/http-foundation/ServerBag.php create mode 100644 vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php create mode 100644 vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php create mode 100644 vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php create mode 100644 vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php create mode 100644 vendor/symfony/http-foundation/Session/Flash/FlashBag.php create mode 100644 vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php create mode 100644 vendor/symfony/http-foundation/Session/Session.php create mode 100644 vendor/symfony/http-foundation/Session/SessionBagInterface.php create mode 100644 vendor/symfony/http-foundation/Session/SessionInterface.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/MetadataBag.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php create mode 100644 vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php create mode 100644 vendor/symfony/http-foundation/StreamedResponse.php create mode 100644 vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php create mode 100644 vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ApacheRequestTest.php create mode 100644 vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/CookieTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php create mode 100644 vendor/symfony/http-foundation/Tests/File/FakeFile.php create mode 100644 vendor/symfony/http-foundation/Tests/File/FileTest.php create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/test create mode 100644 vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif create mode 100644 vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php create mode 100644 vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php create mode 100644 vendor/symfony/http-foundation/Tests/FileBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/HeaderBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/IpUtilsTest.php create mode 100644 vendor/symfony/http-foundation/Tests/JsonResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ParameterBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/RedirectResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/RequestMatcherTest.php create mode 100644 vendor/symfony/http-foundation/Tests/RequestStackTest.php create mode 100644 vendor/symfony/http-foundation/Tests/RequestTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/ResponseTestCase.php create mode 100644 vendor/symfony/http-foundation/Tests/ServerBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/SessionTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php create mode 100644 vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php create mode 100644 vendor/symfony/http-foundation/Tests/StreamedResponseTest.php create mode 100644 vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng create mode 100644 vendor/symfony/http-foundation/Tests/schema/iana-registry.rng create mode 100644 vendor/symfony/http-foundation/composer.json create mode 100644 vendor/symfony/http-foundation/phpunit.xml.dist create mode 100644 vendor/symfony/http-kernel/.gitignore create mode 100644 vendor/symfony/http-kernel/Bundle/Bundle.php create mode 100644 vendor/symfony/http-kernel/Bundle/BundleInterface.php create mode 100644 vendor/symfony/http-kernel/CHANGELOG.md create mode 100644 vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php create mode 100644 vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php create mode 100644 vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php create mode 100644 vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php create mode 100644 vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php create mode 100644 vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php create mode 100644 vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php create mode 100644 vendor/symfony/http-kernel/Client.php create mode 100644 vendor/symfony/http-kernel/Config/EnvParametersResource.php create mode 100644 vendor/symfony/http-kernel/Config/FileLocator.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php create mode 100644 vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php create mode 100644 vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ControllerReference.php create mode 100644 vendor/symfony/http-kernel/Controller/ControllerResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php create mode 100644 vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php create mode 100644 vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php create mode 100644 vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php create mode 100644 vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php create mode 100644 vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php create mode 100644 vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/DataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php create mode 100644 vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/EventDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php create mode 100644 vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php create mode 100644 vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php create mode 100644 vendor/symfony/http-kernel/Debug/FileLinkFormatter.php create mode 100644 vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/Extension.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php create mode 100644 vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php create mode 100644 vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php create mode 100644 vendor/symfony/http-kernel/Event/FilterControllerEvent.php create mode 100644 vendor/symfony/http-kernel/Event/FilterResponseEvent.php create mode 100644 vendor/symfony/http-kernel/Event/FinishRequestEvent.php create mode 100644 vendor/symfony/http-kernel/Event/GetResponseEvent.php create mode 100644 vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php create mode 100644 vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php create mode 100644 vendor/symfony/http-kernel/Event/KernelEvent.php create mode 100644 vendor/symfony/http-kernel/Event/PostResponseEvent.php create mode 100644 vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/DumpListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/ExceptionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/FragmentListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/LocaleListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/ProfilerListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/ResponseListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/RouterListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/SaveSessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/SessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/SurrogateListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/TestSessionListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/TranslatorListener.php create mode 100644 vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php create mode 100644 vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/BadRequestHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/ConflictHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/GoneHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/HttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php create mode 100644 vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/NotFoundHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php create mode 100644 vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php create mode 100644 vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/FragmentHandler.php create mode 100644 vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php create mode 100644 vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php create mode 100644 vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php create mode 100644 vendor/symfony/http-kernel/HttpCache/Esi.php create mode 100644 vendor/symfony/http-kernel/HttpCache/HttpCache.php create mode 100644 vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php create mode 100644 vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php create mode 100644 vendor/symfony/http-kernel/HttpCache/Ssi.php create mode 100644 vendor/symfony/http-kernel/HttpCache/Store.php create mode 100644 vendor/symfony/http-kernel/HttpCache/StoreInterface.php create mode 100644 vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php create mode 100644 vendor/symfony/http-kernel/HttpKernel.php create mode 100644 vendor/symfony/http-kernel/HttpKernelInterface.php create mode 100644 vendor/symfony/http-kernel/Kernel.php create mode 100644 vendor/symfony/http-kernel/KernelEvents.php create mode 100644 vendor/symfony/http-kernel/KernelInterface.php create mode 100644 vendor/symfony/http-kernel/LICENSE create mode 100644 vendor/symfony/http-kernel/Log/DebugLoggerInterface.php create mode 100644 vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php create mode 100644 vendor/symfony/http-kernel/Profiler/Profile.php create mode 100644 vendor/symfony/http-kernel/Profiler/Profiler.php create mode 100644 vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php create mode 100644 vendor/symfony/http-kernel/README.md create mode 100644 vendor/symfony/http-kernel/TerminableInterface.php create mode 100644 vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php create mode 100644 vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php create mode 100644 vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/ClientTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php create mode 100644 vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php create mode 100644 vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/BadRequestHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/ConflictHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/GoneHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/HttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/MethodNotAllowedHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/NotFoundHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/PreconditionFailedHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/PreconditionRequiredHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/ServiceUnavailableHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/hide.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/bar.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/FooCommand.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/Resources/FooBundle/foo.txt create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php create mode 100644 vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php create mode 100644 vendor/symfony/http-kernel/Tests/HttpKernelTest.php create mode 100644 vendor/symfony/http-kernel/Tests/KernelTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Logger.php create mode 100644 vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php create mode 100644 vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php create mode 100644 vendor/symfony/http-kernel/Tests/TestHttpKernel.php create mode 100644 vendor/symfony/http-kernel/Tests/UriSignerTest.php create mode 100644 vendor/symfony/http-kernel/UriSigner.php create mode 100644 vendor/symfony/http-kernel/composer.json create mode 100644 vendor/symfony/http-kernel/phpunit.xml.dist 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/upperCase.php create mode 100644 vendor/symfony/polyfill-mbstring/bootstrap.php create mode 100644 vendor/symfony/polyfill-mbstring/composer.json create mode 100644 vendor/symfony/routing/.gitignore create mode 100644 vendor/symfony/routing/Annotation/Route.php create mode 100644 vendor/symfony/routing/CHANGELOG.md create mode 100644 vendor/symfony/routing/CompiledRoute.php create mode 100644 vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php create mode 100644 vendor/symfony/routing/Exception/ExceptionInterface.php create mode 100644 vendor/symfony/routing/Exception/InvalidParameterException.php create mode 100644 vendor/symfony/routing/Exception/MethodNotAllowedException.php create mode 100644 vendor/symfony/routing/Exception/MissingMandatoryParametersException.php create mode 100644 vendor/symfony/routing/Exception/ResourceNotFoundException.php create mode 100644 vendor/symfony/routing/Exception/RouteNotFoundException.php create mode 100644 vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php create mode 100644 vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php create mode 100644 vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php create mode 100644 vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php create mode 100644 vendor/symfony/routing/Generator/UrlGenerator.php create mode 100644 vendor/symfony/routing/Generator/UrlGeneratorInterface.php create mode 100644 vendor/symfony/routing/LICENSE create mode 100644 vendor/symfony/routing/Loader/AnnotationClassLoader.php create mode 100644 vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php create mode 100644 vendor/symfony/routing/Loader/AnnotationFileLoader.php create mode 100644 vendor/symfony/routing/Loader/ClosureLoader.php create mode 100644 vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php create mode 100644 vendor/symfony/routing/Loader/DirectoryLoader.php create mode 100644 vendor/symfony/routing/Loader/ObjectRouteLoader.php create mode 100644 vendor/symfony/routing/Loader/PhpFileLoader.php create mode 100644 vendor/symfony/routing/Loader/XmlFileLoader.php create mode 100644 vendor/symfony/routing/Loader/YamlFileLoader.php create mode 100644 vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd create mode 100644 vendor/symfony/routing/Matcher/Dumper/DumperCollection.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/DumperRoute.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php create mode 100644 vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php create mode 100644 vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php create mode 100644 vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php create mode 100644 vendor/symfony/routing/Matcher/RequestMatcherInterface.php create mode 100644 vendor/symfony/routing/Matcher/TraceableUrlMatcher.php create mode 100644 vendor/symfony/routing/Matcher/UrlMatcher.php create mode 100644 vendor/symfony/routing/Matcher/UrlMatcherInterface.php create mode 100644 vendor/symfony/routing/README.md create mode 100644 vendor/symfony/routing/RequestContext.php create mode 100644 vendor/symfony/routing/RequestContextAwareInterface.php create mode 100644 vendor/symfony/routing/Route.php create mode 100644 vendor/symfony/routing/RouteCollection.php create mode 100644 vendor/symfony/routing/RouteCollectionBuilder.php create mode 100644 vendor/symfony/routing/RouteCompiler.php create mode 100644 vendor/symfony/routing/RouteCompilerInterface.php create mode 100644 vendor/symfony/routing/Router.php create mode 100644 vendor/symfony/routing/RouterInterface.php create mode 100644 vendor/symfony/routing/Tests/Annotation/RouteTest.php create mode 100644 vendor/symfony/routing/Tests/CompiledRouteTest.php create mode 100644 vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/annotated.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/bad_format.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/bar.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.apache create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.apache create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/empty.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/file_resource.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/foo.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/foo1.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/incomplete.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/list_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/list_null_values.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/map_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/map_null_values.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/missing_id.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/missing_path.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalid.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalid.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/null_values.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/special_route_name.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/validpattern.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/validpattern.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/validpattern.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/validresource.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/validresource.xml create mode 100644 vendor/symfony/routing/Tests/Fixtures/validresource.yml create mode 100644 vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/withdoctype.xml create mode 100644 vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php create mode 100644 vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php create mode 100644 vendor/symfony/routing/Tests/RequestContextTest.php create mode 100644 vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php create mode 100644 vendor/symfony/routing/Tests/RouteCollectionTest.php create mode 100644 vendor/symfony/routing/Tests/RouteCompilerTest.php create mode 100644 vendor/symfony/routing/Tests/RouteTest.php create mode 100644 vendor/symfony/routing/Tests/RouterTest.php create mode 100644 vendor/symfony/routing/composer.json create mode 100644 vendor/symfony/routing/phpunit.xml.dist create mode 100644 web/.htaccess create mode 100644 web/index.php diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..be665ae6 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "silex/silex": "~2.0" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..2eed6fe6 --- /dev/null +++ b/composer.lock @@ -0,0 +1,593 @@ +{ + "_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#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "8d4b4feb048277b50f1d95a289dad3e0", + "content-hash": "85677501dca45c0b473c524c2f1a82b0", + "packages": [ + { + "name": "pimple/pimple", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2015-09-11 15:10:35" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10 12:19:37" + }, + { + "name": "silex/silex", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex.git", + "reference": "d5a9d9af14a1424ddecc3da481769cf64e7d3b34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex/zipball/d5a9d9af14a1424ddecc3da481769cf64e7d3b34", + "reference": "d5a9d9af14a1424ddecc3da481769cf64e7d3b34", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "symfony/event-dispatcher": "~2.8|^3.0", + "symfony/http-foundation": "~2.8|^3.0", + "symfony/http-kernel": "~2.8|^3.0", + "symfony/routing": "~2.8|^3.0" + }, + "replace": { + "silex/api": "self.version", + "silex/providers": "self.version" + }, + "require-dev": { + "doctrine/dbal": "~2.2", + "monolog/monolog": "^1.4.1", + "swiftmailer/swiftmailer": "~5", + "symfony/asset": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/doctrine-bridge": "~2.8|^3.0", + "symfony/dom-crawler": "~2.8|^3.0", + "symfony/expression-language": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/intl": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/options-resolver": "~2.8|^3.0", + "symfony/phpunit-bridge": "^3.2", + "symfony/process": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/serializer": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "symfony/var-dumper": "~2.8|^3.0", + "symfony/web-link": "^3.3", + "twig/twig": "~1.28|~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Silex\\": "src/Silex" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "The PHP micro-framework based on the Symfony Components", + "homepage": "http://silex.sensiolabs.org", + "keywords": [ + "microframework" + ], + "time": "2017-05-03 15:21:42" + }, + { + "name": "symfony/debug", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/e9c50482841ef696e8fa1470d950a79c8921f45d", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2017-06-01 21:01:25" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "4054a102470665451108f9b59305c79176ef98f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4054a102470665451108f9b59305c79176ef98f0", + "reference": "4054a102470665451108f9b59305c79176ef98f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-06-04 18:15:29" + }, + { + "name": "symfony/http-foundation", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2017-06-05 13:06:51" + }, + { + "name": "symfony/http-kernel", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/be8280f7fa8e95b86514f1e1be997668a53b2888", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2017-06-06 03:59:58" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-09 14:24:12" + }, + { + "name": "symfony/routing", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/39804eeafea5cca851946e1eed122eb94459fdb4", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~3.3" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2017-06-02 09:51:43" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/db/ThiagoEvangelistaRamos.sql b/db/ThiagoEvangelistaRamos.sql new file mode 100644 index 00000000..8ede4628 --- /dev/null +++ b/db/ThiagoEvangelistaRamos.sql @@ -0,0 +1,46 @@ +CREATE DATABASE video_repository_db; + +CREATE TABLE video ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `title` VARCHAR(255) NOT NULL, + `category_id` BIGINT(20) NOT NULL, + `description` text NULL, + `filename` VARCHAR(255) NOT NULL, + `duration` time, + `active` VARCHAR(255) NOT NULL, + `creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `modification_date` datetime NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (`id`) ) +ENGINE = InnoDB; + + +CREATE TABLE category ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `title` VARCHAR(255) NOT NULL, + `description` BIGINT(20) NOT NULL, + `active` VARCHAR(255) NOT NULL, + `creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `modification_date` datetime NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (`id`) ) +ENGINE = InnoDB; + +CREATE TABLE user ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `name` VARCHAR(255) NOT NULL, + `email` VARCHAR(255) NOT NULL, + `active` VARCHAR(255) NOT NULL, + `creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `modification_date` datetime NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (`id`) ) +ENGINE = InnoDB; + + +CREATE TABLE user_access_token ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `user_id` BIGINT(20) NOT NULL, + `access_token` VARCHAR(255) NOT NULL, + `active` VARCHAR(255) NOT NULL, + `creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + `modification_date` datetime NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (`id`) ) +ENGINE = InnoDB; diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 00000000..e0741fde --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + private $classMapAuthoritative = false; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 00000000..1a281248 --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 2016 Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000..7a91153b --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 00000000..c3cd0229 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,10 @@ + array($vendorDir . '/pimple/pimple/src'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 00000000..a24f71fe --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,17 @@ + array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), + 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), + 'Silex\\' => array($vendorDir . '/silex/silex/src/Silex'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 00000000..ebd14970 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION'); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInitffc6f475c67d41d430373c18ea299e37::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInitffc6f475c67d41d430373c18ea299e37::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequireffc6f475c67d41d430373c18ea299e37($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequireffc6f475c67d41d430373c18ea299e37($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 00000000..402cc136 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,84 @@ + __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Component\\Routing\\' => 26, + 'Symfony\\Component\\HttpKernel\\' => 29, + 'Symfony\\Component\\HttpFoundation\\' => 33, + 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\Debug\\' => 24, + 'Silex\\' => 6, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Component\\Routing\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/routing', + ), + 'Symfony\\Component\\HttpKernel\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-kernel', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Symfony\\Component\\Debug\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/debug', + ), + 'Silex\\' => + array ( + 0 => __DIR__ . '/..' . '/silex/silex/src/Silex', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + ); + + public static $prefixesPsr0 = array ( + 'P' => + array ( + 'Pimple' => + array ( + 0 => __DIR__ . '/..' . '/pimple/pimple/src', + ), + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitffc6f475c67d41d430373c18ea299e37::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitffc6f475c67d41d430373c18ea299e37::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInitffc6f475c67d41d430373c18ea299e37::$prefixesPsr0; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 00000000..496a1d14 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,594 @@ +[ + { + "name": "symfony/routing", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/39804eeafea5cca851946e1eed122eb94459fdb4", + "reference": "39804eeafea5cca851946e1eed122eb94459fdb4", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~3.3" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "time": "2017-06-02 09:51:43", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ] + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.4.0", + "version_normalized": "1.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2017-06-09 14:24:12", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/http-foundation", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "reference": "80eb5a1f968448b77da9e8b2c0827f6e8d767846", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "time": "2017-06-05 13:06:51", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "4054a102470665451108f9b59305c79176ef98f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4054a102470665451108f9b59305c79176ef98f0", + "reference": "4054a102470665451108f9b59305c79176ef98f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2017-06-04 18:15:29", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com" + }, + { + "name": "psr/log", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-10-10 12:19:37", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "symfony/debug", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/e9c50482841ef696e8fa1470d950a79c8921f45d", + "reference": "e9c50482841ef696e8fa1470d950a79c8921f45d", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "time": "2017-06-01 21:01:25", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/http-kernel", + "version": "v3.3.2", + "version_normalized": "3.3.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/be8280f7fa8e95b86514f1e1be997668a53b2888", + "reference": "be8280f7fa8e95b86514f1e1be997668a53b2888", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0", + "symfony/debug": "~2.8|~3.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "time": "2017-06-06 03:59:58", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com" + }, + { + "name": "pimple/pimple", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2015-09-11 15:10:35", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ] + }, + { + "name": "silex/silex", + "version": "v2.1.0", + "version_normalized": "2.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex.git", + "reference": "d5a9d9af14a1424ddecc3da481769cf64e7d3b34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex/zipball/d5a9d9af14a1424ddecc3da481769cf64e7d3b34", + "reference": "d5a9d9af14a1424ddecc3da481769cf64e7d3b34", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "symfony/event-dispatcher": "~2.8|^3.0", + "symfony/http-foundation": "~2.8|^3.0", + "symfony/http-kernel": "~2.8|^3.0", + "symfony/routing": "~2.8|^3.0" + }, + "replace": { + "silex/api": "self.version", + "silex/providers": "self.version" + }, + "require-dev": { + "doctrine/dbal": "~2.2", + "monolog/monolog": "^1.4.1", + "swiftmailer/swiftmailer": "~5", + "symfony/asset": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/doctrine-bridge": "~2.8|^3.0", + "symfony/dom-crawler": "~2.8|^3.0", + "symfony/expression-language": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/intl": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/options-resolver": "~2.8|^3.0", + "symfony/phpunit-bridge": "^3.2", + "symfony/process": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/serializer": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "symfony/var-dumper": "~2.8|^3.0", + "symfony/web-link": "^3.3", + "twig/twig": "~1.28|~2.0" + }, + "time": "2017-05-03 15:21:42", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Silex\\": "src/Silex" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "The PHP micro-framework based on the Symfony Components", + "homepage": "http://silex.sensiolabs.org", + "keywords": [ + "microframework" + ] + } +] diff --git a/vendor/pimple/pimple/.gitignore b/vendor/pimple/pimple/.gitignore new file mode 100644 index 00000000..c089b095 --- /dev/null +++ b/vendor/pimple/pimple/.gitignore @@ -0,0 +1,3 @@ +phpunit.xml +composer.lock +/vendor/ diff --git a/vendor/pimple/pimple/.travis.yml b/vendor/pimple/pimple/.travis.yml new file mode 100644 index 00000000..5f8bb7c9 --- /dev/null +++ b/vendor/pimple/pimple/.travis.yml @@ -0,0 +1,32 @@ +language: php + +env: + matrix: + - PIMPLE_EXT=no + - PIMPLE_EXT=yes + global: + - REPORT_EXIT_STATUS=1 + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm + +before_script: + - composer self-update + - COMPOSER_ROOT_VERSION=dev-master composer dump-autoload + - if [ "$PIMPLE_EXT" == "yes" ]; then sh -c "cd ext/pimple && phpize && ./configure && make && sudo make install"; fi + - if [ "$PIMPLE_EXT" == "yes" ]; then echo "extension=pimple.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi + +script: + - cd ext/pimple + - if [ "$PIMPLE_EXT" == "yes" ]; then yes n | make test | tee output ; grep -E 'Tests failed +. +0' output; fi + - cd ../.. + - phpunit + +matrix: + exclude: + - php: hhvm + env: PIMPLE_EXT=yes diff --git a/vendor/pimple/pimple/CHANGELOG b/vendor/pimple/pimple/CHANGELOG new file mode 100644 index 00000000..cc679972 --- /dev/null +++ b/vendor/pimple/pimple/CHANGELOG @@ -0,0 +1,35 @@ +* 3.0.2 (2015-09-11) + + * refactored the C extension + * minor non-significant changes + +* 3.0.1 (2015-07-30) + + * simplified some code + * fixed a segfault in the C extension + +* 3.0.0 (2014-07-24) + + * removed the Pimple class alias (use Pimple\Container instead) + +* 2.1.1 (2014-07-24) + + * fixed compiler warnings for the C extension + * fixed code when dealing with circular references + +* 2.1.0 (2014-06-24) + + * moved the Pimple to Pimple\Container (with a BC layer -- Pimple is now a + deprecated alias which will be removed in Pimple 3.0) + * added Pimple\ServiceProviderInterface (and Pimple::register()) + +* 2.0.0 (2014-02-10) + + * changed extend to automatically re-assign the extended service and keep it as shared or factory + (to keep BC, extend still returns the extended service) + * changed services to be shared by default (use factory() for factory + services) + +* 1.0.0 + + * initial version diff --git a/vendor/pimple/pimple/LICENSE b/vendor/pimple/pimple/LICENSE new file mode 100644 index 00000000..d7949e2f --- /dev/null +++ b/vendor/pimple/pimple/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2009-2015 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/pimple/pimple/README.rst b/vendor/pimple/pimple/README.rst new file mode 100644 index 00000000..93fb35a8 --- /dev/null +++ b/vendor/pimple/pimple/README.rst @@ -0,0 +1,201 @@ +Pimple +====== + +.. caution:: + + This is the documentation for Pimple 3.x. If you are using Pimple 1.x, read + the `Pimple 1.x documentation`_. Reading the Pimple 1.x code is also a good + way to learn more about how to create a simple Dependency Injection + Container (recent versions of Pimple are more focused on performance). + +Pimple is a small Dependency Injection Container for PHP. + +Installation +------------ + +Before using Pimple in your project, add it to your ``composer.json`` file: + +.. code-block:: bash + + $ ./composer.phar require pimple/pimple ~3.0 + +Alternatively, Pimple is also available as a PHP C extension: + +.. code-block:: bash + + $ git clone https://github.com/silexphp/Pimple + $ cd Pimple/ext/pimple + $ phpize + $ ./configure + $ make + $ make install + +Usage +----- + +Creating a container is a matter of creating a ``Container`` instance: + +.. code-block:: php + + use Pimple\Container; + + $container = new Container(); + +As many other dependency injection containers, Pimple manages two different +kind of data: **services** and **parameters**. + +Defining Services +~~~~~~~~~~~~~~~~~ + +A service is an object that does something as part of a larger system. Examples +of services: a database connection, a templating engine, or a mailer. Almost +any **global** object can be a service. + +Services are defined by **anonymous functions** that return an instance of an +object: + +.. code-block:: php + + // define some services + $container['session_storage'] = function ($c) { + return new SessionStorage('SESSION_ID'); + }; + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + +Notice that the anonymous function has access to the current container +instance, allowing references to other services or parameters. + +As objects are only created when you get them, the order of the definitions +does not matter. + +Using the defined services is also very easy: + +.. code-block:: php + + // get the session object + $session = $container['session']; + + // the above call is roughly equivalent to the following code: + // $storage = new SessionStorage('SESSION_ID'); + // $session = new Session($storage); + +Defining Factory Services +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, each time you get a service, Pimple returns the **same instance** +of it. If you want a different instance to be returned for all calls, wrap your +anonymous function with the ``factory()`` method + +.. code-block:: php + + $container['session'] = $container->factory(function ($c) { + return new Session($c['session_storage']); + }); + +Now, each call to ``$container['session']`` returns a new instance of the +session. + +Defining Parameters +~~~~~~~~~~~~~~~~~~~ + +Defining a parameter allows to ease the configuration of your container from +the outside and to store global values: + +.. code-block:: php + + // define some parameters + $container['cookie_name'] = 'SESSION_ID'; + $container['session_storage_class'] = 'SessionStorage'; + +If you change the ``session_storage`` service definition like below: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + +You can now easily change the cookie name by overriding the +``session_storage_class`` parameter instead of redefining the service +definition. + +Protecting Parameters +~~~~~~~~~~~~~~~~~~~~~ + +Because Pimple sees anonymous functions as service definitions, you need to +wrap anonymous functions with the ``protect()`` method to store them as +parameters: + +.. code-block:: php + + $container['random_func'] = $container->protect(function () { + return rand(); + }); + +Modifying Services after Definition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases you may want to modify a service definition after it has been +defined. You can use the ``extend()`` method to define additional code to be +run on your service just after it is created: + +.. code-block:: php + + $container['session_storage'] = function ($c) { + return new $c['session_storage_class']($c['cookie_name']); + }; + + $container->extend('session_storage', function ($storage, $c) { + $storage->...(); + + return $storage; + }); + +The first argument is the name of the service to extend, the second a function +that gets access to the object instance and the container. + +Extending a Container +~~~~~~~~~~~~~~~~~~~~~ + +If you use the same libraries over and over, you might want to reuse some +services from one project to the next one; package your services into a +**provider** by implementing ``Pimple\ServiceProviderInterface``: + +.. code-block:: php + + use Pimple\Container; + + class FooProvider implements Pimple\ServiceProviderInterface + { + public function register(Container $pimple) + { + // register some services and parameters + // on $pimple + } + } + +Then, register the provider on a Container: + +.. code-block:: php + + $pimple->register(new FooProvider()); + +Fetching the Service Creation Function +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you access an object, Pimple automatically calls the anonymous function +that you defined, which creates the service object for you. If you want to get +raw access to this function, you can use the ``raw()`` method: + +.. code-block:: php + + $container['session'] = function ($c) { + return new Session($c['session_storage']); + }; + + $sessionFunction = $container->raw('session'); + +.. _Pimple 1.x documentation: https://github.com/silexphp/Pimple/tree/1.1 diff --git a/vendor/pimple/pimple/composer.json b/vendor/pimple/pimple/composer.json new file mode 100644 index 00000000..a5268f16 --- /dev/null +++ b/vendor/pimple/pimple/composer.json @@ -0,0 +1,25 @@ +{ + "name": "pimple/pimple", + "type": "library", + "description": "Pimple, a simple Dependency Injection Container", + "keywords": ["dependency injection", "container"], + "homepage": "http://pimple.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-0": { "Pimple": "src/" } + }, + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + } +} diff --git a/vendor/pimple/pimple/ext/pimple/.gitignore b/vendor/pimple/pimple/ext/pimple/.gitignore new file mode 100644 index 00000000..1861088a --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/.gitignore @@ -0,0 +1,30 @@ +*.sw* +.deps +Makefile +Makefile.fragments +Makefile.global +Makefile.objects +acinclude.m4 +aclocal.m4 +build/ +config.cache +config.guess +config.h +config.h.in +config.log +config.nice +config.status +config.sub +configure +configure.in +install-sh +libtool +ltmain.sh +missing +mkinstalldirs +run-tests.php +*.loT +.libs/ +modules/ +*.la +*.lo diff --git a/vendor/pimple/pimple/ext/pimple/README.md b/vendor/pimple/pimple/ext/pimple/README.md new file mode 100644 index 00000000..7b39eb29 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/README.md @@ -0,0 +1,12 @@ +This is Pimple 2 implemented in C + +* PHP >= 5.3 +* Not tested under Windows, might work + +Install +======= + + > phpize + > ./configure + > make + > make install diff --git a/vendor/pimple/pimple/ext/pimple/config.m4 b/vendor/pimple/pimple/ext/pimple/config.m4 new file mode 100644 index 00000000..c9ba17dd --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/config.m4 @@ -0,0 +1,63 @@ +dnl $Id$ +dnl config.m4 for extension pimple + +dnl Comments in this file start with the string 'dnl'. +dnl Remove where necessary. This file will not work +dnl without editing. + +dnl If your extension references something external, use with: + +dnl PHP_ARG_WITH(pimple, for pimple support, +dnl Make sure that the comment is aligned: +dnl [ --with-pimple Include pimple support]) + +dnl Otherwise use enable: + +PHP_ARG_ENABLE(pimple, whether to enable pimple support, +dnl Make sure that the comment is aligned: +[ --enable-pimple Enable pimple support]) + +if test "$PHP_PIMPLE" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-pimple -> check with-path + dnl SEARCH_PATH="/usr/local /usr" # you might want to change this + dnl SEARCH_FOR="/include/pimple.h" # you most likely want to change this + dnl if test -r $PHP_PIMPLE/$SEARCH_FOR; then # path given as parameter + dnl PIMPLE_DIR=$PHP_PIMPLE + dnl else # search default path list + dnl AC_MSG_CHECKING([for pimple files in default path]) + dnl for i in $SEARCH_PATH ; do + dnl if test -r $i/$SEARCH_FOR; then + dnl PIMPLE_DIR=$i + dnl AC_MSG_RESULT(found in $i) + dnl fi + dnl done + dnl fi + dnl + dnl if test -z "$PIMPLE_DIR"; then + dnl AC_MSG_RESULT([not found]) + dnl AC_MSG_ERROR([Please reinstall the pimple distribution]) + dnl fi + + dnl # --with-pimple -> add include path + dnl PHP_ADD_INCLUDE($PIMPLE_DIR/include) + + dnl # --with-pimple -> check for lib and symbol presence + dnl LIBNAME=pimple # you may want to change this + dnl LIBSYMBOL=pimple # you most likely want to change this + + dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + dnl [ + dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $PIMPLE_DIR/lib, PIMPLE_SHARED_LIBADD) + dnl AC_DEFINE(HAVE_PIMPLELIB,1,[ ]) + dnl ],[ + dnl AC_MSG_ERROR([wrong pimple lib version or lib not found]) + dnl ],[ + dnl -L$PIMPLE_DIR/lib -lm + dnl ]) + dnl + dnl PHP_SUBST(PIMPLE_SHARED_LIBADD) + + PHP_NEW_EXTENSION(pimple, pimple.c, $ext_shared) +fi diff --git a/vendor/pimple/pimple/ext/pimple/config.w32 b/vendor/pimple/pimple/ext/pimple/config.w32 new file mode 100644 index 00000000..39857b32 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +// If your extension references something external, use ARG_WITH +// ARG_WITH("pimple", "for pimple support", "no"); + +// Otherwise, use ARG_ENABLE +// ARG_ENABLE("pimple", "enable pimple support", "no"); + +if (PHP_PIMPLE != "no") { + EXTENSION("pimple", "pimple.c"); +} + diff --git a/vendor/pimple/pimple/ext/pimple/php_pimple.h b/vendor/pimple/pimple/ext/pimple/php_pimple.h new file mode 100644 index 00000000..49431f08 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/php_pimple.h @@ -0,0 +1,121 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 Fabien Potencier + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PHP_PIMPLE_H +#define PHP_PIMPLE_H + +extern zend_module_entry pimple_module_entry; +#define phpext_pimple_ptr &pimple_module_entry + +#ifdef PHP_WIN32 +# define PHP_PIMPLE_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_PIMPLE_API __attribute__ ((visibility("default"))) +#else +# define PHP_PIMPLE_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#define PIMPLE_VERSION "3.0.2" +#define PIMPLE_NS "Pimple" + +#define PIMPLE_DEFAULT_ZVAL_CACHE_NUM 5 +#define PIMPLE_DEFAULT_ZVAL_VALUES_NUM 10 + +zend_module_entry *get_module(void); + +PHP_MINIT_FUNCTION(pimple); +PHP_MINFO_FUNCTION(pimple); + +PHP_METHOD(Pimple, __construct); +PHP_METHOD(Pimple, factory); +PHP_METHOD(Pimple, protect); +PHP_METHOD(Pimple, raw); +PHP_METHOD(Pimple, extend); +PHP_METHOD(Pimple, keys); +PHP_METHOD(Pimple, register); +PHP_METHOD(Pimple, offsetSet); +PHP_METHOD(Pimple, offsetUnset); +PHP_METHOD(Pimple, offsetGet); +PHP_METHOD(Pimple, offsetExists); + +PHP_METHOD(PimpleClosure, invoker); + +typedef struct _pimple_bucket_value { + zval *value; /* Must be the first element */ + zval *raw; + zend_object_handle handle_num; + enum { + PIMPLE_IS_PARAM = 0, + PIMPLE_IS_SERVICE = 2 + } type; + zend_bool initialized; + zend_fcall_info_cache fcc; +} pimple_bucket_value; + +typedef struct _pimple_object { + zend_object zobj; + HashTable values; + HashTable factories; + HashTable protected; +} pimple_object; + +typedef struct _pimple_closure_object { + zend_object zobj; + zval *callable; + zval *factory; +} pimple_closure_object; + +static const char sensiolabs_logo[] = ""; + +static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); +static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC); + +static void pimple_bucket_dtor(pimple_bucket_value *bucket); +static void pimple_free_bucket(pimple_bucket_value *bucket); + +static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC); +static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC); +static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC); +static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC); +static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC); +static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC); + +static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC); +static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC); +static zend_function *pimple_closure_get_constructor(zval * TSRMLS_DC); +static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC); + +#ifdef ZTS +#define PIMPLE_G(v) TSRMG(pimple_globals_id, zend_pimple_globals *, v) +#else +#define PIMPLE_G(v) (pimple_globals.v) +#endif + +#endif /* PHP_PIMPLE_H */ + diff --git a/vendor/pimple/pimple/ext/pimple/pimple.c b/vendor/pimple/pimple/ext/pimple/pimple.c new file mode 100644 index 00000000..239c01d6 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/pimple.c @@ -0,0 +1,922 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 Fabien Potencier + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_pimple.h" +#include "pimple_compat.h" +#include "zend_interfaces.h" +#include "zend.h" +#include "Zend/zend_closures.h" +#include "ext/spl/spl_exceptions.h" +#include "Zend/zend_exceptions.h" +#include "main/php_output.h" +#include "SAPI.h" + +static zend_class_entry *pimple_ce; +static zend_object_handlers pimple_object_handlers; +static zend_class_entry *pimple_closure_ce; +static zend_class_entry *pimple_serviceprovider_ce; +static zend_object_handlers pimple_closure_object_handlers; +static zend_internal_function pimple_closure_invoker_function; + +#define FETCH_DIM_HANDLERS_VARS pimple_object *pimple_obj = NULL; \ + ulong index; \ + pimple_obj = (pimple_object *)zend_object_store_get_object(object TSRMLS_CC); \ + +#define PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS do { \ + if (ce != pimple_ce) { \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetget"), (void **)&function); \ + if (function->common.scope != ce) { /* if the function is not defined in this actual class */ \ + pimple_object_handlers.read_dimension = pimple_object_read_dimension; /* then overwrite the handler to use custom one */ \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetset"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.write_dimension = pimple_object_write_dimension; \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetexists"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.has_dimension = pimple_object_has_dimension; \ + } \ + zend_hash_find(&ce->function_table, ZEND_STRS("offsetunset"), (void **)&function); \ + if (function->common.scope != ce) { \ + pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \ + } \ + } else { \ + pimple_object_handlers.read_dimension = pimple_object_read_dimension; \ + pimple_object_handlers.write_dimension = pimple_object_write_dimension; \ + pimple_object_handlers.has_dimension = pimple_object_has_dimension; \ + pimple_object_handlers.unset_dimension = pimple_object_unset_dimension; \ + }\ + } while(0); + +#define PIMPLE_CALL_CB do { \ + zend_fcall_info_argn(&fci TSRMLS_CC, 1, &object); \ + fci.size = sizeof(fci); \ + fci.object_ptr = retval->fcc.object_ptr; \ + fci.function_name = retval->value; \ + fci.no_separation = 1; \ + fci.retval_ptr_ptr = &retval_ptr_ptr; \ +\ + zend_call_function(&fci, &retval->fcc TSRMLS_CC); \ + efree(fci.params); \ + if (EG(exception)) { \ + return EG(uninitialized_zval_ptr); \ + } \ + } while(0); + +ZEND_BEGIN_ARG_INFO_EX(arginfo___construct, 0, 0, 0) +ZEND_ARG_ARRAY_INFO(0, value, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetset, 0, 0, 2) +ZEND_ARG_INFO(0, offset) +ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetget, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetexists, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetunset, 0, 0, 1) +ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_factory, 0, 0, 1) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_protect, 0, 0, 1) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_raw, 0, 0, 1) +ZEND_ARG_INFO(0, id) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_extend, 0, 0, 2) +ZEND_ARG_INFO(0, id) +ZEND_ARG_INFO(0, callable) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_register, 0, 0, 1) +ZEND_ARG_OBJ_INFO(0, provider, Pimple\\ServiceProviderInterface, 0) +ZEND_ARG_ARRAY_INFO(0, values, 1) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_serviceprovider_register, 0, 0, 1) +ZEND_ARG_OBJ_INFO(0, pimple, Pimple\\Container, 0) +ZEND_END_ARG_INFO() + +static const zend_function_entry pimple_ce_functions[] = { + PHP_ME(Pimple, __construct, arginfo___construct, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, factory, arginfo_factory, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, protect, arginfo_protect, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, raw, arginfo_raw, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, extend, arginfo_extend, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, keys, arginfo_keys, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, register, arginfo_register, ZEND_ACC_PUBLIC) + + PHP_ME(Pimple, offsetSet, arginfo_offsetset, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetGet, arginfo_offsetget, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetExists, arginfo_offsetexists, ZEND_ACC_PUBLIC) + PHP_ME(Pimple, offsetUnset, arginfo_offsetunset, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static const zend_function_entry pimple_serviceprovider_iface_ce_functions[] = { + PHP_ABSTRACT_ME(ServiceProviderInterface, register, arginfo_serviceprovider_register) + PHP_FE_END +}; + +static void pimple_closure_free_object_storage(pimple_closure_object *obj TSRMLS_DC) +{ + zend_object_std_dtor(&obj->zobj TSRMLS_CC); + if (obj->factory) { + zval_ptr_dtor(&obj->factory); + } + if (obj->callable) { + zval_ptr_dtor(&obj->callable); + } + efree(obj); +} + +static void pimple_free_object_storage(pimple_object *obj TSRMLS_DC) +{ + zend_hash_destroy(&obj->factories); + zend_hash_destroy(&obj->protected); + zend_hash_destroy(&obj->values); + zend_object_std_dtor(&obj->zobj TSRMLS_CC); + efree(obj); +} + +static void pimple_free_bucket(pimple_bucket_value *bucket) +{ + if (bucket->raw) { + zval_ptr_dtor(&bucket->raw); + } +} + +static zend_object_value pimple_closure_object_create(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value retval; + pimple_closure_object *pimple_closure_obj = NULL; + + pimple_closure_obj = ecalloc(1, sizeof(pimple_closure_object)); + ZEND_OBJ_INIT(&pimple_closure_obj->zobj, ce); + + pimple_closure_object_handlers.get_constructor = pimple_closure_get_constructor; + retval.handlers = &pimple_closure_object_handlers; + retval.handle = zend_objects_store_put(pimple_closure_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_closure_free_object_storage, NULL TSRMLS_CC); + + return retval; +} + +static zend_function *pimple_closure_get_constructor(zval *obj TSRMLS_DC) +{ + zend_error(E_ERROR, "Pimple\\ContainerClosure is an internal class and cannot be instantiated"); + + return NULL; +} + +static int pimple_closure_get_closure(zval *obj, zend_class_entry **ce_ptr, union _zend_function **fptr_ptr, zval **zobj_ptr TSRMLS_DC) +{ + *zobj_ptr = obj; + *ce_ptr = Z_OBJCE_P(obj); + *fptr_ptr = (zend_function *)&pimple_closure_invoker_function; + + return SUCCESS; +} + +static zend_object_value pimple_object_create(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value retval; + pimple_object *pimple_obj = NULL; + zend_function *function = NULL; + + pimple_obj = emalloc(sizeof(pimple_object)); + ZEND_OBJ_INIT(&pimple_obj->zobj, ce); + + PIMPLE_OBJECT_HANDLE_INHERITANCE_OBJECT_HANDLERS + + retval.handlers = &pimple_object_handlers; + retval.handle = zend_objects_store_put(pimple_obj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) pimple_free_object_storage, NULL TSRMLS_CC); + + zend_hash_init(&pimple_obj->factories, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + zend_hash_init(&pimple_obj->protected, PIMPLE_DEFAULT_ZVAL_CACHE_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + zend_hash_init(&pimple_obj->values, PIMPLE_DEFAULT_ZVAL_VALUES_NUM, NULL, (dtor_func_t)pimple_bucket_dtor, 0); + + return retval; +} + +static void pimple_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value pimple_value = {0}, *found_value = NULL; + ulong hash; + + pimple_zval_to_pimpleval(value, &pimple_value TSRMLS_CC); + + if (!offset) {/* $p[] = 'foo' when not overloaded */ + zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(value); + return; + } + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + hash = zend_hash_func(Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_hash_quick_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void **)&found_value); + if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) { + pimple_free_bucket(&pimple_value); + zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%s\".", Z_STRVAL_P(offset)); + return; + } + if (zend_hash_quick_update(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, hash, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) { + pimple_free_bucket(&pimple_value); + return; + } + Z_ADDREF_P(value); + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + zend_hash_index_find(&pimple_obj->values, index, (void **)&found_value); + if (found_value && found_value->type == PIMPLE_IS_SERVICE && found_value->initialized == 1) { + pimple_free_bucket(&pimple_value); + zend_throw_exception_ex(spl_ce_RuntimeException, 0 TSRMLS_CC, "Cannot override frozen service \"%ld\".", index); + return; + } + if (zend_hash_index_update(&pimple_obj->values, index, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL) == FAILURE) { + pimple_free_bucket(&pimple_value); + return; + } + Z_ADDREF_P(value); + break; + case IS_NULL: /* $p[] = 'foo' when overloaded */ + zend_hash_next_index_insert(&pimple_obj->values, (void *)&pimple_value, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(value); + break; + default: + pimple_free_bucket(&pimple_value); + zend_error(E_WARNING, "Unsupported offset type"); + } +} + +static void pimple_object_unset_dimension(zval *object, zval *offset TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + zend_symtable_del(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_symtable_del(&pimple_obj->factories, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + zend_symtable_del(&pimple_obj->protected, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1); + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + zend_hash_index_del(&pimple_obj->values, index); + zend_hash_index_del(&pimple_obj->factories, index); + zend_hash_index_del(&pimple_obj->protected, index); + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + } +} + +static int pimple_object_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value *retval = NULL; + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == SUCCESS) { + switch (check_empty) { + case 0: /* isset */ + return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;) */ + case 1: /* empty */ + default: + return zend_is_true(retval->value); + } + } + return 0; + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == SUCCESS) { + switch (check_empty) { + case 0: /* isset */ + return 1; /* Differs from PHP behavior (Z_TYPE_P(retval->value) != IS_NULL;)*/ + case 1: /* empty */ + default: + return zend_is_true(retval->value); + } + } + return 0; + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + return 0; + } +} + +static zval *pimple_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC) +{ + FETCH_DIM_HANDLERS_VARS + + pimple_bucket_value *retval = NULL; + zend_fcall_info fci = {0}; + zval *retval_ptr_ptr = NULL; + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pimple_obj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void **)&retval) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset)); + return EG(uninitialized_zval_ptr); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pimple_obj->values, index, (void **)&retval) == FAILURE) { + return EG(uninitialized_zval_ptr); + } + break; + case IS_NULL: /* $p[][3] = 'foo' first dim access */ + return EG(uninitialized_zval_ptr); + break; + default: + zend_error(E_WARNING, "Unsupported offset type"); + return EG(uninitialized_zval_ptr); + } + + if(retval->type == PIMPLE_IS_PARAM) { + return retval->value; + } + + if (zend_hash_index_exists(&pimple_obj->protected, retval->handle_num)) { + /* Service is protected, return the value every time */ + return retval->value; + } + + if (zend_hash_index_exists(&pimple_obj->factories, retval->handle_num)) { + /* Service is a factory, call it everytime and never cache its result */ + PIMPLE_CALL_CB + Z_DELREF_P(retval_ptr_ptr); /* fetch dim addr will increment refcount */ + return retval_ptr_ptr; + } + + if (retval->initialized == 1) { + /* Service has already been called, return its cached value */ + return retval->value; + } + + ALLOC_INIT_ZVAL(retval->raw); + MAKE_COPY_ZVAL(&retval->value, retval->raw); + + PIMPLE_CALL_CB + + retval->initialized = 1; + zval_ptr_dtor(&retval->value); + retval->value = retval_ptr_ptr; + + return retval->value; +} + +static int pimple_zval_is_valid_callback(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC) +{ + if (Z_TYPE_P(_zval) != IS_OBJECT) { + return FAILURE; + } + + if (_pimple_bucket_value->fcc.called_scope) { + return SUCCESS; + } + + if (Z_OBJ_HANDLER_P(_zval, get_closure) && Z_OBJ_HANDLER_P(_zval, get_closure)(_zval, &_pimple_bucket_value->fcc.calling_scope, &_pimple_bucket_value->fcc.function_handler, &_pimple_bucket_value->fcc.object_ptr TSRMLS_CC) == SUCCESS) { + _pimple_bucket_value->fcc.called_scope = _pimple_bucket_value->fcc.calling_scope; + return SUCCESS; + } else { + return FAILURE; + } +} + +static int pimple_zval_to_pimpleval(zval *_zval, pimple_bucket_value *_pimple_bucket_value TSRMLS_DC) +{ + _pimple_bucket_value->value = _zval; + + if (Z_TYPE_P(_zval) != IS_OBJECT) { + return PIMPLE_IS_PARAM; + } + + if (pimple_zval_is_valid_callback(_zval, _pimple_bucket_value TSRMLS_CC) == SUCCESS) { + _pimple_bucket_value->type = PIMPLE_IS_SERVICE; + _pimple_bucket_value->handle_num = Z_OBJ_HANDLE_P(_zval); + } + + return PIMPLE_IS_SERVICE; +} + +static void pimple_bucket_dtor(pimple_bucket_value *bucket) +{ + zval_ptr_dtor(&bucket->value); + pimple_free_bucket(bucket); +} + +PHP_METHOD(Pimple, protect) +{ + zval *protected = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value bucket = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &protected) == FAILURE) { + return; + } + + if (pimple_zval_is_valid_callback(protected, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(spl_ce_InvalidArgumentException, "Callable is not a Closure or invokable object.", 0 TSRMLS_CC); + return; + } + + pimple_zval_to_pimpleval(protected, &bucket TSRMLS_CC); + pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (zend_hash_index_update(&pobj->protected, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) { + Z_ADDREF_P(protected); + RETURN_ZVAL(protected, 1 , 0); + } else { + pimple_free_bucket(&bucket); + } + RETURN_FALSE; +} + +PHP_METHOD(Pimple, raw) +{ + zval *offset = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value *value = NULL; + ulong index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset)); + RETURN_NULL(); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) { + RETURN_NULL(); + } + break; + case IS_NULL: + default: + zend_error(E_WARNING, "Unsupported offset type"); + } + + if (value->raw) { + RETVAL_ZVAL(value->raw, 1, 0); + } else { + RETVAL_ZVAL(value->value, 1, 0); + } +} + +PHP_METHOD(Pimple, extend) +{ + zval *offset = NULL, *callable = NULL, *pimple_closure_obj = NULL; + pimple_bucket_value bucket = {0}, *value = NULL; + pimple_object *pobj = NULL; + pimple_closure_object *pcobj = NULL; + ulong index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &callable) == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + switch (Z_TYPE_P(offset)) { + case IS_STRING: + if (zend_symtable_find(&pobj->values, Z_STRVAL_P(offset), Z_STRLEN_P(offset)+1, (void *)&value) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" is not defined.", Z_STRVAL_P(offset)); + RETURN_NULL(); + } + if (value->type != PIMPLE_IS_SERVICE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%s\" does not contain an object definition.", Z_STRVAL_P(offset)); + RETURN_NULL(); + } + break; + case IS_DOUBLE: + case IS_BOOL: + case IS_LONG: + if (Z_TYPE_P(offset) == IS_DOUBLE) { + index = (ulong)Z_DVAL_P(offset); + } else { + index = Z_LVAL_P(offset); + } + if (zend_hash_index_find(&pobj->values, index, (void *)&value) == FAILURE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" is not defined.", index); + RETURN_NULL(); + } + if (value->type != PIMPLE_IS_SERVICE) { + zend_throw_exception_ex(spl_ce_InvalidArgumentException, 0 TSRMLS_CC, "Identifier \"%ld\" does not contain an object definition.", index); + RETURN_NULL(); + } + break; + case IS_NULL: + default: + zend_error(E_WARNING, "Unsupported offset type"); + } + + if (pimple_zval_is_valid_callback(callable, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(spl_ce_InvalidArgumentException, "Extension service definition is not a Closure or invokable object.", 0 TSRMLS_CC); + RETURN_NULL(); + } + pimple_free_bucket(&bucket); + + ALLOC_INIT_ZVAL(pimple_closure_obj); + object_init_ex(pimple_closure_obj, pimple_closure_ce); + + pcobj = zend_object_store_get_object(pimple_closure_obj TSRMLS_CC); + pcobj->callable = callable; + pcobj->factory = value->value; + Z_ADDREF_P(callable); + Z_ADDREF_P(value->value); + + if (zend_hash_index_exists(&pobj->factories, value->handle_num)) { + pimple_zval_to_pimpleval(pimple_closure_obj, &bucket TSRMLS_CC); + zend_hash_index_del(&pobj->factories, value->handle_num); + zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL); + Z_ADDREF_P(pimple_closure_obj); + } + + pimple_object_write_dimension(getThis(), offset, pimple_closure_obj TSRMLS_CC); + + RETVAL_ZVAL(pimple_closure_obj, 1, 1); +} + +PHP_METHOD(Pimple, keys) +{ + HashPosition pos; + pimple_object *pobj = NULL; + zval **value = NULL; + zval *endval = NULL; + char *str_index = NULL; + int str_len; + ulong num_index; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + pobj = zend_object_store_get_object(getThis() TSRMLS_CC); + array_init_size(return_value, zend_hash_num_elements(&pobj->values)); + + zend_hash_internal_pointer_reset_ex(&pobj->values, &pos); + + while(zend_hash_get_current_data_ex(&pobj->values, (void **)&value, &pos) == SUCCESS) { + MAKE_STD_ZVAL(endval); + switch (zend_hash_get_current_key_ex(&pobj->values, &str_index, (uint *)&str_len, &num_index, 0, &pos)) { + case HASH_KEY_IS_STRING: + ZVAL_STRINGL(endval, str_index, str_len - 1, 1); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL); + break; + case HASH_KEY_IS_LONG: + ZVAL_LONG(endval, num_index); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &endval, sizeof(zval *), NULL); + break; + } + zend_hash_move_forward_ex(&pobj->values, &pos); + } +} + +PHP_METHOD(Pimple, factory) +{ + zval *factory = NULL; + pimple_object *pobj = NULL; + pimple_bucket_value bucket = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &factory) == FAILURE) { + return; + } + + if (pimple_zval_is_valid_callback(factory, &bucket TSRMLS_CC) == FAILURE) { + pimple_free_bucket(&bucket); + zend_throw_exception(spl_ce_InvalidArgumentException, "Service definition is not a Closure or invokable object.", 0 TSRMLS_CC); + return; + } + + pimple_zval_to_pimpleval(factory, &bucket TSRMLS_CC); + pobj = (pimple_object *)zend_object_store_get_object(getThis() TSRMLS_CC); + + if (zend_hash_index_update(&pobj->factories, bucket.handle_num, (void *)&bucket, sizeof(pimple_bucket_value), NULL) == SUCCESS) { + Z_ADDREF_P(factory); + RETURN_ZVAL(factory, 1 , 0); + } else { + pimple_free_bucket(&bucket); + } + + RETURN_FALSE; +} + +PHP_METHOD(Pimple, offsetSet) +{ + zval *offset = NULL, *value = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &offset, &value) == FAILURE) { + return; + } + + pimple_object_write_dimension(getThis(), offset, value TSRMLS_CC); +} + +PHP_METHOD(Pimple, offsetGet) +{ + zval *offset = NULL, *retval = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + retval = pimple_object_read_dimension(getThis(), offset, 0 TSRMLS_CC); + + RETVAL_ZVAL(retval, 1, 0); +} + +PHP_METHOD(Pimple, offsetUnset) +{ + zval *offset = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + pimple_object_unset_dimension(getThis(), offset TSRMLS_CC); +} + +PHP_METHOD(Pimple, offsetExists) +{ + zval *offset = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &offset) == FAILURE) { + return; + } + + RETVAL_BOOL(pimple_object_has_dimension(getThis(), offset, 1 TSRMLS_CC)); +} + +PHP_METHOD(Pimple, register) +{ + zval *provider; + zval **data; + zval *retval = NULL; + zval key; + + HashTable *array = NULL; + HashPosition pos; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|h", &provider, pimple_serviceprovider_ce, &array) == FAILURE) { + return; + } + + RETVAL_ZVAL(getThis(), 1, 0); + + zend_call_method_with_1_params(&provider, Z_OBJCE_P(provider), NULL, "register", &retval, getThis()); + + if (retval) { + zval_ptr_dtor(&retval); + } + + if (!array) { + return; + } + + zend_hash_internal_pointer_reset_ex(array, &pos); + + while(zend_hash_get_current_data_ex(array, (void **)&data, &pos) == SUCCESS) { + zend_hash_get_current_key_zval_ex(array, &key, &pos); + pimple_object_write_dimension(getThis(), &key, *data TSRMLS_CC); + zend_hash_move_forward_ex(array, &pos); + } +} + +PHP_METHOD(Pimple, __construct) +{ + zval *values = NULL, **pData = NULL, offset; + HashPosition pos; + char *str_index = NULL; + zend_uint str_length; + ulong num_index; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &values) == FAILURE || !values) { + return; + } + + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos); + while (zend_hash_has_more_elements_ex(Z_ARRVAL_P(values), &pos) == SUCCESS) { + zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&pData, &pos); + zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &str_index, &str_length, &num_index, 0, &pos); + INIT_ZVAL(offset); + if (zend_hash_get_current_key_type_ex(Z_ARRVAL_P(values), &pos) == HASH_KEY_IS_LONG) { + ZVAL_LONG(&offset, num_index); + } else { + ZVAL_STRINGL(&offset, str_index, (str_length - 1), 0); + } + pimple_object_write_dimension(getThis(), &offset, *pData TSRMLS_CC); + zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos); + } +} + +/* + * This is PHP code snippet handling extend()s calls : + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + */ +PHP_METHOD(PimpleClosure, invoker) +{ + pimple_closure_object *pcobj = NULL; + zval *arg = NULL, *retval = NULL, *newretval = NULL; + zend_fcall_info fci = {0}; + zval **args[2]; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) { + return; + } + + pcobj = zend_object_store_get_object(getThis() TSRMLS_CC); + + fci.function_name = pcobj->factory; + args[0] = &arg; + zend_fcall_info_argp(&fci TSRMLS_CC, 1, args); + fci.retval_ptr_ptr = &retval; + fci.size = sizeof(fci); + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) { + efree(fci.params); + return; /* Should here return default zval */ + } + + efree(fci.params); + memset(&fci, 0, sizeof(fci)); + fci.size = sizeof(fci); + + fci.function_name = pcobj->callable; + args[0] = &retval; + args[1] = &arg; + zend_fcall_info_argp(&fci TSRMLS_CC, 2, args); + fci.retval_ptr_ptr = &newretval; + + if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE || EG(exception)) { + efree(fci.params); + zval_ptr_dtor(&retval); + return; + } + + efree(fci.params); + zval_ptr_dtor(&retval); + + RETVAL_ZVAL(newretval, 1 ,1); +} + +PHP_MINIT_FUNCTION(pimple) +{ + zend_class_entry tmp_pimple_ce, tmp_pimple_closure_ce, tmp_pimple_serviceprovider_iface_ce; + INIT_NS_CLASS_ENTRY(tmp_pimple_ce, PIMPLE_NS, "Container", pimple_ce_functions); + INIT_NS_CLASS_ENTRY(tmp_pimple_closure_ce, PIMPLE_NS, "ContainerClosure", NULL); + INIT_NS_CLASS_ENTRY(tmp_pimple_serviceprovider_iface_ce, PIMPLE_NS, "ServiceProviderInterface", pimple_serviceprovider_iface_ce_functions); + + tmp_pimple_ce.create_object = pimple_object_create; + tmp_pimple_closure_ce.create_object = pimple_closure_object_create; + + pimple_ce = zend_register_internal_class(&tmp_pimple_ce TSRMLS_CC); + zend_class_implements(pimple_ce TSRMLS_CC, 1, zend_ce_arrayaccess); + + pimple_closure_ce = zend_register_internal_class(&tmp_pimple_closure_ce TSRMLS_CC); + pimple_closure_ce->ce_flags |= ZEND_ACC_FINAL_CLASS; + + pimple_serviceprovider_ce = zend_register_internal_interface(&tmp_pimple_serviceprovider_iface_ce TSRMLS_CC); + + memcpy(&pimple_closure_object_handlers, zend_get_std_object_handlers(), sizeof(*zend_get_std_object_handlers())); + pimple_object_handlers = std_object_handlers; + pimple_closure_object_handlers.get_closure = pimple_closure_get_closure; + + pimple_closure_invoker_function.function_name = "Pimple closure internal invoker"; + pimple_closure_invoker_function.fn_flags |= ZEND_ACC_CLOSURE; + pimple_closure_invoker_function.handler = ZEND_MN(PimpleClosure_invoker); + pimple_closure_invoker_function.num_args = 1; + pimple_closure_invoker_function.required_num_args = 1; + pimple_closure_invoker_function.scope = pimple_closure_ce; + pimple_closure_invoker_function.type = ZEND_INTERNAL_FUNCTION; + pimple_closure_invoker_function.module = &pimple_module_entry; + + return SUCCESS; +} + +PHP_MINFO_FUNCTION(pimple) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "SensioLabs Pimple C support", "enabled"); + php_info_print_table_row(2, "Pimple supported version", PIMPLE_VERSION); + php_info_print_table_end(); + + php_info_print_box_start(0); + php_write((void *)ZEND_STRL("SensioLabs Pimple C support developed by Julien Pauli") TSRMLS_CC); + if (!sapi_module.phpinfo_as_text) { + php_write((void *)ZEND_STRL(sensiolabs_logo) TSRMLS_CC); + } + php_info_print_box_end(); +} + +zend_module_entry pimple_module_entry = { + STANDARD_MODULE_HEADER, + "pimple", + NULL, + PHP_MINIT(pimple), + NULL, + NULL, + NULL, + PHP_MINFO(pimple), + PIMPLE_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_PIMPLE +ZEND_GET_MODULE(pimple) +#endif diff --git a/vendor/pimple/pimple/ext/pimple/pimple_compat.h b/vendor/pimple/pimple/ext/pimple/pimple_compat.h new file mode 100644 index 00000000..d234e174 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/pimple_compat.h @@ -0,0 +1,81 @@ + +/* + * This file is part of Pimple. + * + * Copyright (c) 2014 Fabien Potencier + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PIMPLE_COMPAT_H_ +#define PIMPLE_COMPAT_H_ + +#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ + +#define PHP_5_0_X_API_NO 220040412 +#define PHP_5_1_X_API_NO 220051025 +#define PHP_5_2_X_API_NO 220060519 +#define PHP_5_3_X_API_NO 220090626 +#define PHP_5_4_X_API_NO 220100525 +#define PHP_5_5_X_API_NO 220121212 +#define PHP_5_6_X_API_NO 220131226 + +#define IS_PHP_56 ZEND_EXTENSION_API_NO == PHP_5_6_X_API_NO +#define IS_AT_LEAST_PHP_56 ZEND_EXTENSION_API_NO >= PHP_5_6_X_API_NO + +#define IS_PHP_55 ZEND_EXTENSION_API_NO == PHP_5_5_X_API_NO +#define IS_AT_LEAST_PHP_55 ZEND_EXTENSION_API_NO >= PHP_5_5_X_API_NO + +#define IS_PHP_54 ZEND_EXTENSION_API_NO == PHP_5_4_X_API_NO +#define IS_AT_LEAST_PHP_54 ZEND_EXTENSION_API_NO >= PHP_5_4_X_API_NO + +#define IS_PHP_53 ZEND_EXTENSION_API_NO == PHP_5_3_X_API_NO +#define IS_AT_LEAST_PHP_53 ZEND_EXTENSION_API_NO >= PHP_5_3_X_API_NO + +#if IS_PHP_53 +#define object_properties_init(obj, ce) do { \ + zend_hash_copy(obj->properties, &ce->default_properties, zval_copy_property_ctor(ce), NULL, sizeof(zval *)); \ + } while (0); +#endif + +#define ZEND_OBJ_INIT(obj, ce) do { \ + zend_object_std_init(obj, ce TSRMLS_CC); \ + object_properties_init((obj), (ce)); \ + } while(0); + +#if IS_PHP_53 || IS_PHP_54 +static void zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos) { + Bucket *p; + + p = pos ? (*pos) : ht->pInternalPointer; + + if (!p) { + Z_TYPE_P(key) = IS_NULL; + } else if (p->nKeyLength) { + Z_TYPE_P(key) = IS_STRING; + Z_STRVAL_P(key) = estrndup(p->arKey, p->nKeyLength - 1); + Z_STRLEN_P(key) = p->nKeyLength - 1; + } else { + Z_TYPE_P(key) = IS_LONG; + Z_LVAL_P(key) = p->h; + } +} +#endif + +#endif /* PIMPLE_COMPAT_H_ */ diff --git a/vendor/pimple/pimple/ext/pimple/tests/001.phpt b/vendor/pimple/pimple/ext/pimple/tests/001.phpt new file mode 100644 index 00000000..0809ea23 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/001.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test for read_dim/write_dim handlers +--SKIPIF-- + +--FILE-- + + +--EXPECTF-- +foo +42 +foo2 +foo99 +baz +strstr \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/002.phpt b/vendor/pimple/pimple/ext/pimple/tests/002.phpt new file mode 100644 index 00000000..7b56d2c1 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Test for constructor +--SKIPIF-- + +--FILE-- +'foo')); +var_dump($p[42]); +?> +--EXPECT-- +NULL +string(3) "foo" diff --git a/vendor/pimple/pimple/ext/pimple/tests/003.phpt b/vendor/pimple/pimple/ext/pimple/tests/003.phpt new file mode 100644 index 00000000..a22cfa35 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/003.phpt @@ -0,0 +1,16 @@ +--TEST-- +Test empty dimensions +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(42) +string(3) "bar" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/004.phpt b/vendor/pimple/pimple/ext/pimple/tests/004.phpt new file mode 100644 index 00000000..1e1d2513 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/004.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test has/unset dim handlers +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(42) +NULL +bool(true) +bool(false) +bool(true) +bool(true) \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/005.phpt b/vendor/pimple/pimple/ext/pimple/tests/005.phpt new file mode 100644 index 00000000..0479ee05 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/005.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test simple class inheritance +--SKIPIF-- + +--FILE-- +someAttr; +?> +--EXPECT-- +string(3) "hit" +foo +fooAttr \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/006.phpt b/vendor/pimple/pimple/ext/pimple/tests/006.phpt new file mode 100644 index 00000000..cfe8a119 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/006.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test complex class inheritance +--SKIPIF-- + +--FILE-- + 'bar', 88 => 'baz'); + +$p = new TestPimple($defaultValues); +$p[42] = 'foo'; +var_dump($p[42]); +var_dump($p[0]); +?> +--EXPECT-- +string(13) "hit offsetset" +string(27) "hit offsetget in TestPimple" +string(25) "hit offsetget in MyPimple" +string(3) "foo" +string(27) "hit offsetget in TestPimple" +string(25) "hit offsetget in MyPimple" +string(3) "baz" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/007.phpt b/vendor/pimple/pimple/ext/pimple/tests/007.phpt new file mode 100644 index 00000000..5aac6838 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/007.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test for read_dim/write_dim handlers +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +foo +42 \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/008.phpt b/vendor/pimple/pimple/ext/pimple/tests/008.phpt new file mode 100644 index 00000000..db7eeec4 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/008.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test frozen services +--SKIPIF-- + +--FILE-- + +--EXPECTF-- diff --git a/vendor/pimple/pimple/ext/pimple/tests/009.phpt b/vendor/pimple/pimple/ext/pimple/tests/009.phpt new file mode 100644 index 00000000..bb05ea29 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/009.phpt @@ -0,0 +1,13 @@ +--TEST-- +Test service is called as callback, and only once +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/010.phpt b/vendor/pimple/pimple/ext/pimple/tests/010.phpt new file mode 100644 index 00000000..badce014 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/010.phpt @@ -0,0 +1,45 @@ +--TEST-- +Test service is called as callback for every callback type +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +callme +called +Foo::bar +array(2) { + [0]=> + string(3) "Foo" + [1]=> + string(3) "bar" +} \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/011.phpt b/vendor/pimple/pimple/ext/pimple/tests/011.phpt new file mode 100644 index 00000000..6682ab8e --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/011.phpt @@ -0,0 +1,19 @@ +--TEST-- +Test service callback throwing an exception +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +all right! \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/012.phpt b/vendor/pimple/pimple/ext/pimple/tests/012.phpt new file mode 100644 index 00000000..4c6ac486 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/012.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test service factory +--SKIPIF-- + +--FILE-- +factory($f = function() { var_dump('called-1'); return 'ret-1';}); + +$p[] = $f; + +$p[] = function () { var_dump('called-2'); return 'ret-2'; }; + +var_dump($p[0]); +var_dump($p[0]); +var_dump($p[1]); +var_dump($p[1]); +?> +--EXPECTF-- +string(8) "called-1" +string(5) "ret-1" +string(8) "called-1" +string(5) "ret-1" +string(8) "called-2" +string(5) "ret-2" +string(5) "ret-2" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/013.phpt b/vendor/pimple/pimple/ext/pimple/tests/013.phpt new file mode 100644 index 00000000..f419958c --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/013.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test keys() +--SKIPIF-- + +--FILE-- +keys()); + +$p['foo'] = 'bar'; +$p[] = 'foo'; + +var_dump($p->keys()); + +unset($p['foo']); + +var_dump($p->keys()); +?> +--EXPECTF-- +array(0) { +} +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(0) +} +array(1) { + [0]=> + int(0) +} \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/014.phpt b/vendor/pimple/pimple/ext/pimple/tests/014.phpt new file mode 100644 index 00000000..ac937213 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/014.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test raw() +--SKIPIF-- + +--FILE-- +raw('foo')); +var_dump($p[42]); + +unset($p['foo']); + +try { + $p->raw('foo'); + echo "expected exception"; +} catch (InvalidArgumentException $e) { } +--EXPECTF-- +string(8) "called-2" +string(5) "ret-2" +object(Closure)#%i (0) { +} +string(8) "called-2" +string(5) "ret-2" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/015.phpt b/vendor/pimple/pimple/ext/pimple/tests/015.phpt new file mode 100644 index 00000000..314f008a --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/015.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test protect() +--SKIPIF-- + +--FILE-- +protect($f); + +var_dump($p['foo']); +--EXPECTF-- +object(Closure)#%i (0) { +} \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/016.phpt b/vendor/pimple/pimple/ext/pimple/tests/016.phpt new file mode 100644 index 00000000..e55edb0a --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/016.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test extend() +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { var_dump($w); return 'bar'; }); /* $callable in code above */ + +var_dump($c('param')); +--EXPECTF-- +string(5) "param" +string(3) "foo" +string(3) "bar" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/017.phpt b/vendor/pimple/pimple/ext/pimple/tests/017.phpt new file mode 100644 index 00000000..bac23ce0 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/017.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test extend() with exception in service extension +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { throw new BadMethodCallException; }); + +try { + $p[12]; + echo "Exception expected"; +} catch (BadMethodCallException $e) { } +--EXPECTF-- diff --git a/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt b/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt new file mode 100644 index 00000000..8f881d6e --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/017_1.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test extend() with exception in service factory +--SKIPIF-- + +--FILE-- +extend(12, function ($w) { return 'foobar'; }); + +try { + $p[12]; + echo "Exception expected"; +} catch (BadMethodCallException $e) { } +--EXPECTF-- diff --git a/vendor/pimple/pimple/ext/pimple/tests/018.phpt b/vendor/pimple/pimple/ext/pimple/tests/018.phpt new file mode 100644 index 00000000..27c12a14 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/018.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test register() +--SKIPIF-- + +--FILE-- +register(new Foo, array(42 => 'bar')); + +var_dump($p[42]); +--EXPECTF-- +object(Pimple\Container)#1 (0) { +} +string(3) "bar" \ No newline at end of file diff --git a/vendor/pimple/pimple/ext/pimple/tests/019.phpt b/vendor/pimple/pimple/ext/pimple/tests/019.phpt new file mode 100644 index 00000000..28a9aeca --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/019.phpt @@ -0,0 +1,18 @@ +--TEST-- +Test register() returns static and is a fluent interface +--SKIPIF-- + +--FILE-- +register(new Foo)); +--EXPECTF-- +bool(true) diff --git a/vendor/pimple/pimple/ext/pimple/tests/bench.phpb b/vendor/pimple/pimple/ext/pimple/tests/bench.phpb new file mode 100644 index 00000000..8f983e65 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/bench.phpb @@ -0,0 +1,51 @@ +factory($factory); + +$p['factory'] = $factory; + +echo $p['factory']; +echo $p['factory']; +echo $p['factory']; + +} + +echo microtime(true) - $time; diff --git a/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb b/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb new file mode 100644 index 00000000..aec541f0 --- /dev/null +++ b/vendor/pimple/pimple/ext/pimple/tests/bench_shared.phpb @@ -0,0 +1,25 @@ + diff --git a/vendor/pimple/pimple/phpunit.xml.dist b/vendor/pimple/pimple/phpunit.xml.dist new file mode 100644 index 00000000..5c8d487f --- /dev/null +++ b/vendor/pimple/pimple/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + + ./src/Pimple/Tests + + + diff --git a/vendor/pimple/pimple/src/Pimple/Container.php b/vendor/pimple/pimple/src/Pimple/Container.php new file mode 100644 index 00000000..c976431e --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Container.php @@ -0,0 +1,282 @@ +factories = new \SplObjectStorage(); + $this->protected = new \SplObjectStorage(); + + foreach ($values as $key => $value) { + $this->offsetSet($key, $value); + } + } + + /** + * Sets a parameter or an object. + * + * Objects must be defined as Closures. + * + * Allowing any PHP callable leads to difficult to debug problems + * as function names (strings) are callable (creating a function with + * the same name as an existing parameter would break your container). + * + * @param string $id The unique identifier for the parameter or object + * @param mixed $value The value of the parameter or a closure to define an object + * + * @throws \RuntimeException Prevent override of a frozen service + */ + public function offsetSet($id, $value) + { + if (isset($this->frozen[$id])) { + throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id)); + } + + $this->values[$id] = $value; + $this->keys[$id] = true; + } + + /** + * Gets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or an object + * + * @throws \InvalidArgumentException if the identifier is not defined + */ + public function offsetGet($id) + { + if (!isset($this->keys[$id])) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if ( + isset($this->raw[$id]) + || !is_object($this->values[$id]) + || isset($this->protected[$this->values[$id]]) + || !method_exists($this->values[$id], '__invoke') + ) { + return $this->values[$id]; + } + + if (isset($this->factories[$this->values[$id]])) { + return $this->values[$id]($this); + } + + $raw = $this->values[$id]; + $val = $this->values[$id] = $raw($this); + $this->raw[$id] = $raw; + + $this->frozen[$id] = true; + + return $val; + } + + /** + * Checks if a parameter or an object is set. + * + * @param string $id The unique identifier for the parameter or object + * + * @return bool + */ + public function offsetExists($id) + { + return isset($this->keys[$id]); + } + + /** + * Unsets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + */ + public function offsetUnset($id) + { + if (isset($this->keys[$id])) { + if (is_object($this->values[$id])) { + unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]); + } + + unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]); + } + } + + /** + * Marks a callable as being a factory service. + * + * @param callable $callable A service definition to be used as a factory + * + * @return callable The passed callable + * + * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object + */ + public function factory($callable) + { + if (!method_exists($callable, '__invoke')) { + throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.'); + } + + $this->factories->attach($callable); + + return $callable; + } + + /** + * Protects a callable from being interpreted as a service. + * + * This is useful when you want to store a callable as a parameter. + * + * @param callable $callable A callable to protect from being evaluated + * + * @return callable The passed callable + * + * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object + */ + public function protect($callable) + { + if (!method_exists($callable, '__invoke')) { + throw new \InvalidArgumentException('Callable is not a Closure or invokable object.'); + } + + $this->protected->attach($callable); + + return $callable; + } + + /** + * Gets a parameter or the closure defining an object. + * + * @param string $id The unique identifier for the parameter or object + * + * @return mixed The value of the parameter or the closure defining an object + * + * @throws \InvalidArgumentException if the identifier is not defined + */ + public function raw($id) + { + if (!isset($this->keys[$id])) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if (isset($this->raw[$id])) { + return $this->raw[$id]; + } + + return $this->values[$id]; + } + + /** + * Extends an object definition. + * + * Useful when you want to extend an existing object definition, + * without necessarily loading that object. + * + * @param string $id The unique identifier for the object + * @param callable $callable A service definition to extend the original + * + * @return callable The wrapped callable + * + * @throws \InvalidArgumentException if the identifier is not defined or not a service definition + */ + public function extend($id, $callable) + { + if (!isset($this->keys[$id])) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) { + throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id)); + } + + if (!is_object($callable) || !method_exists($callable, '__invoke')) { + throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.'); + } + + $factory = $this->values[$id]; + + $extended = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + + if (isset($this->factories[$factory])) { + $this->factories->detach($factory); + $this->factories->attach($extended); + } + + return $this[$id] = $extended; + } + + /** + * Returns all defined value names. + * + * @return array An array of value names + */ + public function keys() + { + return array_keys($this->values); + } + + /** + * Registers a service provider. + * + * @param ServiceProviderInterface $provider A ServiceProviderInterface instance + * @param array $values An array of values that customizes the provider + * + * @return static + */ + public function register(ServiceProviderInterface $provider, array $values = array()) + { + $provider->register($this); + + foreach ($values as $key => $value) { + $this[$key] = $value; + } + + return $this; + } +} diff --git a/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php new file mode 100644 index 00000000..c004594b --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php @@ -0,0 +1,46 @@ +value = $value; + + return $service; + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php new file mode 100644 index 00000000..33cd4e54 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php @@ -0,0 +1,34 @@ +factory(function () { + return new Service(); + }); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php new file mode 100644 index 00000000..d71b184d --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php @@ -0,0 +1,35 @@ + + */ +class Service +{ + public $value; +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php new file mode 100644 index 00000000..8e5c4c73 --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php @@ -0,0 +1,76 @@ + + */ +class PimpleServiceProviderInterfaceTest extends \PHPUnit_Framework_TestCase +{ + public function testProvider() + { + $pimple = new Container(); + + $pimpleServiceProvider = new Fixtures\PimpleServiceProvider(); + $pimpleServiceProvider->register($pimple); + + $this->assertEquals('value', $pimple['param']); + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testProviderWithRegisterMethod() + { + $pimple = new Container(); + + $pimple->register(new Fixtures\PimpleServiceProvider(), array( + 'anotherParameter' => 'anotherValue', + )); + + $this->assertEquals('value', $pimple['param']); + $this->assertEquals('anotherValue', $pimple['anotherParameter']); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + + $serviceOne = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['factory']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } +} diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php new file mode 100644 index 00000000..918f620d --- /dev/null +++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php @@ -0,0 +1,440 @@ + + */ +class PimpleTest extends \PHPUnit_Framework_TestCase +{ + public function testWithString() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + + $this->assertEquals('value', $pimple['param']); + } + + public function testWithClosure() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']); + } + + public function testServicesShouldBeDifferent() + { + $pimple = new Container(); + $pimple['service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $serviceOne = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testShouldPassContainerAsParameter() + { + $pimple = new Container(); + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + $pimple['container'] = function ($container) { + return $container; + }; + + $this->assertNotSame($pimple, $pimple['service']); + $this->assertSame($pimple, $pimple['container']); + } + + public function testIsset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + $pimple['null'] = null; + + $this->assertTrue(isset($pimple['param'])); + $this->assertTrue(isset($pimple['service'])); + $this->assertTrue(isset($pimple['null'])); + $this->assertFalse(isset($pimple['non_existent'])); + } + + public function testConstructorInjection() + { + $params = array('param' => 'value'); + $pimple = new Container($params); + + $this->assertSame($params['param'], $pimple['param']); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testOffsetGetValidatesKeyIsPresent() + { + $pimple = new Container(); + echo $pimple['foo']; + } + + public function testOffsetGetHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple['foo']); + } + + public function testUnset() + { + $pimple = new Container(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Fixtures\Service(); + }; + + unset($pimple['param'], $pimple['service']); + $this->assertFalse(isset($pimple['param'])); + $this->assertFalse(isset($pimple['service'])); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testShare($service) + { + $pimple = new Container(); + $pimple['shared_service'] = $service; + + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + + $this->assertSame($serviceOne, $serviceTwo); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testProtect($service) + { + $pimple = new Container(); + $pimple['protected'] = $pimple->protect($service); + + $this->assertSame($service, $pimple['protected']); + } + + public function testGlobalFunctionNameAsParameterValue() + { + $pimple = new Container(); + $pimple['global_function'] = 'strlen'; + $this->assertSame('strlen', $pimple['global_function']); + } + + public function testRaw() + { + $pimple = new Container(); + $pimple['service'] = $definition = $pimple->factory(function () { return 'foo'; }); + $this->assertSame($definition, $pimple->raw('service')); + } + + public function testRawHonorsNullValues() + { + $pimple = new Container(); + $pimple['foo'] = null; + $this->assertNull($pimple->raw('foo')); + } + + public function testFluentRegister() + { + $pimple = new Container(); + $this->assertSame($pimple, $pimple->register($this->getMock('Pimple\ServiceProviderInterface'))); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testRawValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->raw('foo'); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testExtend($service) + { + $pimple = new Container(); + $pimple['shared_service'] = function () { + return new Fixtures\Service(); + }; + $pimple['factory_service'] = $pimple->factory(function () { + return new Fixtures\Service(); + }); + + $pimple->extend('shared_service', $service); + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertSame($serviceOne, $serviceTwo); + $this->assertSame($serviceOne->value, $serviceTwo->value); + + $pimple->extend('factory_service', $service); + $serviceOne = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne); + $serviceTwo = $pimple['factory_service']; + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo); + $this->assertNotSame($serviceOne, $serviceTwo); + $this->assertNotSame($serviceOne->value, $serviceTwo->value); + } + + public function testExtendDoesNotLeakWithFactories() + { + if (extension_loaded('pimple')) { + $this->markTestSkipped('Pimple extension does not support this test'); + } + $pimple = new Container(); + + $pimple['foo'] = $pimple->factory(function () { return; }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) { return; }); + unset($pimple['foo']); + + $p = new \ReflectionProperty($pimple, 'values'); + $p->setAccessible(true); + $this->assertEmpty($p->getValue($pimple)); + + $p = new \ReflectionProperty($pimple, 'factories'); + $p->setAccessible(true); + $this->assertCount(0, $p->getValue($pimple)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testExtendValidatesKeyIsPresent() + { + $pimple = new Container(); + $pimple->extend('foo', function () {}); + } + + public function testKeys() + { + $pimple = new Container(); + $pimple['foo'] = 123; + $pimple['bar'] = 123; + + $this->assertEquals(array('foo', 'bar'), $pimple->keys()); + } + + /** @test */ + public function settingAnInvokableObjectShouldTreatItAsFactory() + { + $pimple = new Container(); + $pimple['invokable'] = new Fixtures\Invokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['invokable']); + } + + /** @test */ + public function settingNonInvokableObjectShouldTreatItAsParameter() + { + $pimple = new Container(); + $pimple['non_invokable'] = new Fixtures\NonInvokable(); + + $this->assertInstanceOf('Pimple\Tests\Fixtures\NonInvokable', $pimple['non_invokable']); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Service definition is not a Closure or invokable object. + */ + public function testFactoryFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->factory($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Callable is not a Closure or invokable object. + */ + public function testProtectFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple->protect($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" does not contain an object definition. + */ + public function testExtendFailsForKeysNotContainingServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = $service; + $pimple->extend('foo', function () {}); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Extension service definition is not a Closure or invokable object. + */ + public function testExtendFailsForInvalidServiceDefinitions($service) + { + $pimple = new Container(); + $pimple['foo'] = function () {}; + $pimple->extend('foo', $service); + } + + /** + * Provider for invalid service definitions. + */ + public function badServiceDefinitionProvider() + { + return array( + array(123), + array(new Fixtures\NonInvokable()), + ); + } + + /** + * Provider for service definitions. + */ + public function serviceDefinitionProvider() + { + return array( + array(function ($value) { + $service = new Fixtures\Service(); + $service->value = $value; + + return $service; + }), + array(new Fixtures\Invokable()), + ); + } + + public function testDefiningNewServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['bar']); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Cannot override frozen service "foo". + */ + public function testOverridingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + $pimple['foo'] = function () { + return 'bar'; + }; + } + + public function testRemovingServiceAfterFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $foo = $pimple['foo']; + + unset($pimple['foo']); + $pimple['foo'] = function () { + return 'bar'; + }; + $this->assertSame('bar', $pimple['foo']); + } + + public function testExtendingService() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.bar"; + }); + $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) { + return "$foo.baz"; + }); + $this->assertSame('foo.bar.baz', $pimple['foo']); + } + + public function testExtendingServiceAfterOtherServiceFreeze() + { + $pimple = new Container(); + $pimple['foo'] = function () { + return 'foo'; + }; + $pimple['bar'] = function () { + return 'bar'; + }; + $foo = $pimple['foo']; + + $pimple['bar'] = $pimple->extend('bar', function ($bar, $app) { + return "$bar.baz"; + }); + $this->assertSame('bar.baz', $pimple['bar']); + } +} diff --git a/vendor/psr/log/.gitignore b/vendor/psr/log/.gitignore new file mode 100644 index 00000000..22d0d82f --- /dev/null +++ b/vendor/psr/log/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE new file mode 100644 index 00000000..474c952b --- /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 00000000..90e721af --- /dev/null +++ b/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 00000000..67f852d1 --- /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 00000000..5ea72438 --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,123 @@ +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 + */ + 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 00000000..d8cd682c --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,28 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php new file mode 100644 index 00000000..a0391a52 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php @@ -0,0 +1,140 @@ + ". + * + * 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() + { + $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'), + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} + +class DummyTest +{ + public function __toString() + { + } +} diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 00000000..574bc1cb --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,45 @@ +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. + +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'); + } + + // 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 00000000..87934d70 --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/silex/silex/.gitignore b/vendor/silex/silex/.gitignore new file mode 100644 index 00000000..3d4ff050 --- /dev/null +++ b/vendor/silex/silex/.gitignore @@ -0,0 +1,5 @@ +/phpunit.xml +/vendor +/build +/composer.lock + diff --git a/vendor/silex/silex/.travis.yml b/vendor/silex/silex/.travis.yml new file mode 100644 index 00000000..fed52268 --- /dev/null +++ b/vendor/silex/silex/.travis.yml @@ -0,0 +1,44 @@ +language: php + +sudo: false + +env: + global: + - SYMFONY_DEPRECATIONS_HELPER=weak + +cache: + directories: + - $HOME/.composer/cache/files + +before_install: + - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then phpenv config-rm xdebug.ini; fi + +before_script: + # symfony/* + - sh -c "if [ '$TWIG_VERSION' != '2.0' ]; then sed -i 's/~1.8|~2.0/~1.8/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.0' ]; then sed -i 's/~2\.8|^3\.0/3.0.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.1' ]; then sed -i 's/~2\.8|^3\.0/3.1.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.2' ]; then sed -i 's/~2\.8|^3\.0/3.2.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '' ]; then sed -i 's/~2\.8|^3\.0/2.8.*@dev/g' composer.json; composer update; fi" + - composer install + +script: ./vendor/bin/simple-phpunit + +matrix: + include: + - php: 5.5 + - php: 5.6 + env: TWIG_VERSION=2.0 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.0 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.1 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.2 + - php: 7.0 + - php: 7.1 + - php: hhvm + +cache: + directories: + - .phpunit diff --git a/vendor/silex/silex/LICENSE b/vendor/silex/silex/LICENSE new file mode 100644 index 00000000..b420d719 --- /dev/null +++ b/vendor/silex/silex/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2017 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/silex/silex/README.rst b/vendor/silex/silex/README.rst new file mode 100644 index 00000000..b79e47b6 --- /dev/null +++ b/vendor/silex/silex/README.rst @@ -0,0 +1,64 @@ +Silex, a simple Web Framework +============================= + +Silex is a PHP micro-framework to develop websites based on `Symfony +components`_: + +.. code-block:: php + + get('/hello/{name}', function ($name) use ($app) { + return 'Hello '.$app->escape($name); + }); + + $app->run(); + +Silex works with PHP 5.5.9 or later. + +Installation +------------ + +The recommended way to install Silex is through `Composer`_: + +.. code-block:: bash + + composer require silex/silex "~2.0" + +Alternatively, you can download the `silex.zip`_ file and extract it. + +More Information +---------------- + +Read the `documentation`_ for more information and `changelog +`_ for upgrading information. + +Tests +----- + +To run the test suite, you need `Composer`_ and `PHPUnit`_: + +.. code-block:: bash + + composer install + phpunit + +Community +--------- + +Check out #silex-php on irc.freenode.net. + +License +------- + +Silex is licensed under the MIT license. + +.. _Symfony components: http://symfony.com +.. _Composer: http://getcomposer.org +.. _PHPUnit: https://phpunit.de +.. _silex.zip: http://silex.sensiolabs.org/download +.. _documentation: http://silex.sensiolabs.org/documentation diff --git a/vendor/silex/silex/composer.json b/vendor/silex/silex/composer.json new file mode 100644 index 00000000..aa2f24ee --- /dev/null +++ b/vendor/silex/silex/composer.json @@ -0,0 +1,69 @@ +{ + "name": "silex/silex", + "description": "The PHP micro-framework based on the Symfony Components", + "keywords": ["microframework"], + "homepage": "http://silex.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "symfony/event-dispatcher": "~2.8|^3.0", + "symfony/http-foundation": "~2.8|^3.0", + "symfony/http-kernel": "~2.8|^3.0", + "symfony/routing": "~2.8|^3.0" + }, + "require-dev": { + "symfony/asset": "~2.8|^3.0", + "symfony/expression-language": "~2.8|^3.0", + "symfony/security": "~2.8|^3.0", + "symfony/config": "~2.8|^3.0", + "symfony/form": "~2.8|^3.0", + "symfony/browser-kit": "~2.8|^3.0", + "symfony/css-selector": "~2.8|^3.0", + "symfony/debug": "~2.8|^3.0", + "symfony/dom-crawler": "~2.8|^3.0", + "symfony/finder": "~2.8|^3.0", + "symfony/intl": "~2.8|^3.0", + "symfony/monolog-bridge": "~2.8|^3.0", + "symfony/doctrine-bridge": "~2.8|^3.0", + "symfony/options-resolver": "~2.8|^3.0", + "symfony/phpunit-bridge": "^3.2", + "symfony/process": "~2.8|^3.0", + "symfony/serializer": "~2.8|^3.0", + "symfony/translation": "~2.8|^3.0", + "symfony/twig-bridge": "~2.8|^3.0", + "symfony/validator": "~2.8|^3.0", + "symfony/var-dumper": "~2.8|^3.0", + "twig/twig": "~1.28|~2.0", + "doctrine/dbal": "~2.2", + "swiftmailer/swiftmailer": "~5", + "monolog/monolog": "^1.4.1", + "symfony/web-link": "^3.3" + }, + "replace": { + "silex/api": "self.version", + "silex/providers": "self.version" + }, + "autoload": { + "psr-4": { "Silex\\": "src/Silex" } + }, + "autoload-dev" : { + "psr-4": { "Silex\\Tests\\" : "tests/Silex/Tests" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/silex/silex/doc/changelog.rst b/vendor/silex/silex/doc/changelog.rst new file mode 100644 index 00000000..81beee92 --- /dev/null +++ b/vendor/silex/silex/doc/changelog.rst @@ -0,0 +1,378 @@ +Changelog +========= + +2.1.0 (2017-05-03) +------------------ + +* added more options to security.firewalls +* added WebLink component integration +* added parameters to configure the Twig core extension behavior +* fixed deprecation notices with symfony/twig-bridge 3.2+ in TwigServiceProvider +* added FormRegistry as a service to enable the extension point +* removed the build scripts +* fixed some deprecation warnings +* added support for registering Swiftmailer plugins + +2.0.4 (2016-11-06) +------------------ + +* fixed twig.app_variable definition +* added support for latest versions of Twig 1.x and 2.0 (Twig runtime loaders) +* added support for Symfony 2.3 + +2.0.3 (2016-08-22) +------------------ + +* fixed lazy evaluation of 'monolog.use_error_handler' +* fixed PHP7 type hint on controllers + +2.0.2 (2016-06-14) +------------------ + +* fixed Symfony 3.1 deprecations + +2.0.1 (2016-05-27) +------------------ + +* fixed the silex form extension registration to allow overriding default ones +* removed support for the obsolete Locale Symfony component (uses the Intl one now) +* added support for Symfony 3.1 + +2.0.0 (2016-05-18) +------------------ + +* decoupled the exception handler from HttpKernelServiceProvider +* Switched to BCrypt as the default encoder in the security provider +* added full support for RequestMatcher +* added support for Symfony Guard +* added support for callables in CallbackResolver +* added FormTrait::namedForm() +* added support for delivery_addresses, delivery_whitelist, and sender_address +* added support to register form types / form types extensions / form types guessers as services +* added support for callable in mounts (allow nested route collection to be built easily) +* added support for conditions on routes +* added support for the Symfony VarDumper Component +* added a global Twig variable (an AppVariable instance) +* [BC BREAK] CSRF has been moved to a standalone provider (``form.secret`` is not available anymore) +* added support for the Symfony HttpFoundation Twig bridge extension +* added support for the Symfony Asset Component +* bumped minimum version of Symfony to 2.8 +* bumped minimum version of PHP to 5.5.0 +* Updated Pimple to 3.0 +* Updated session listeners to extends HttpKernel ones +* [BC BREAK] Locale management has been moved to LocaleServiceProvider which must be registered + if you want Silex to manage your locale (must also be registered for the translation service provider) +* [BC BREAK] Provider interfaces moved to Silex\Api namespace, published as + separate package via subtree split +* [BC BREAK] ServiceProviderInterface split in to EventListenerProviderInterface + and BootableProviderInterface +* [BC BREAK] Service Provider support files moved under Silex\Provider + namespace, allowing publishing as separate package via sub-tree split +* ``monolog.exception.logger_filter`` option added to Monolog service provider +* [BC BREAK] ``$app['request']`` service removed, use ``$app['request_stack']`` instead + +1.3.6 (2016-XX-XX) +------------------ + +* n/a + +1.3.5 (2016-01-06) +------------------ + +* fixed typo in SecurityServiceProvider + +1.3.4 (2015-09-15) +------------------ + +* fixed some new deprecations +* fixed translation registration for the validators + +1.3.3 (2015-09-08) +------------------ + +* added support for Symfony 3.0 and Twig 2.0 +* fixed some Form deprecations +* removed deprecated method call in the exception handler +* fixed Swiftmailer spool flushing when spool is not enabled + +1.3.2 (2015-08-24) +------------------ + +* no changes + +1.3.1 (2015-08-04) +------------------ + +* added missing support for the Expression constraint +* fixed the possibility to override translations for validator error messages +* fixed sub-mounts with same name clash +* fixed session logout handler when a firewall is stateless + +1.3.0 (2015-06-05) +------------------ + +* added a `$app['user']` to get the current user (security provider) +* added view handlers +* added support for the OPTIONS HTTP method +* added caching for the Translator provider +* deprecated `$app['exception_handler']->disable()` in favor of `unset($app['exception_handler'])` +* made Silex compatible with Symfony 2.7 an 2.8 (and keep compatibility with Symfony 2.3, 2.5, and 2.6) +* removed deprecated TwigCoreExtension class (register the new HttpFragmentServiceProvider instead) +* bumped minimum version of PHP to 5.3.9 + +1.2.5 (2015-06-04) +------------------ + +* no code changes (last version of the 1.2 branch) + +1.2.4 (2015-04-11) +------------------ + +* fixed the exception message when mounting a collection that doesn't return a ControllerCollection +* fixed Symfony dependencies (Silex 1.2 is not compatible with Symfony 2.7) + +1.2.3 (2015-01-20) +------------------ + +* fixed remember me listener +* fixed translation files loading when they do not exist +* allowed global after middlewares to return responses like route specific ones + +1.2.2 (2014-09-26) +------------------ + +* fixed Translator locale management +* added support for the $app argument in application middlewares (to make it consistent with route middlewares) +* added form.types to the Form provider + +1.2.1 (2014-07-01) +------------------ + +* added support permissions in the Monolog provider +* fixed Switfmailer spool where the event dispatcher is different from the other ones +* fixed locale when changing it on the translator itself + +1.2.0 (2014-03-29) +------------------ + +* Allowed disabling the boot logic of MonologServiceProvider +* Reverted "convert attributes on the request that actually exist" +* [BC BREAK] Routes are now always added in the order of their registration (even for mounted routes) +* Added run() on Route to be able to define the controller code +* Deprecated TwigCoreExtension (register the new HttpFragmentServiceProvider instead) +* Added HttpFragmentServiceProvider +* Allowed a callback to be a method call on a service (before, after, finish, error, on Application; convert, before, after on Controller) + +1.1.3 (2013-XX-XX) +------------------ + +* Fixed translator locale management + +1.1.2 (2013-10-30) +------------------ + +* Added missing "security.hide_user_not_found" support in SecurityServiceProvider +* Fixed event listeners that are registered after the boot via the on() method + +1.0.2 (2013-10-30) +------------------ + +* Fixed SecurityServiceProvider to use null as a fake controller so that routes can be dumped + +1.1.1 (2013-10-11) +------------------ + +* Removed or replaced deprecated Symfony code +* Updated code to take advantages of 2.3 new features +* Only convert attributes on the request that actually exist. + +1.1.0 (2013-07-04) +------------------ + +* Support for any ``Psr\Log\LoggerInterface`` as opposed to the monolog-bridge + one. +* Made dispatcher proxy methods ``on``, ``before``, ``after`` and ``error`` + lazy, so that they will not instantiate the dispatcher early. +* Dropped support for 2.1 and 2.2 versions of Symfony. + +1.0.1 (2013-07-04) +------------------ + +* Fixed RedirectableUrlMatcher::redirect() when Silex is configured to use a logger +* Make ``DoctrineServiceProvider`` multi-db support lazy. + +1.0.0 (2013-05-03) +------------------ + +* **2013-04-12**: Added support for validators as services. + +* **2013-04-01**: Added support for host matching with symfony 2.2:: + + $app->match('/', function() { + // app-specific action + })->host('example.com'); + + $app->match('/', function ($user) { + // user-specific action + })->host('{user}.example.com'); + +* **2013-03-08**: Added support for form type extensions and guessers as + services. + +* **2013-03-08**: Added support for remember-me via the + ``RememberMeServiceProvider``. + +* **2013-02-07**: Added ``Application::sendFile()`` to ease sending + ``BinaryFileResponse``. + +* **2012-11-05**: Filters have been renamed to application middlewares in the + documentation. + +* **2012-11-05**: The ``before()``, ``after()``, ``error()``, and ``finish()`` + listener priorities now set the priority of the underlying Symfony event + instead of a custom one before. + +* **2012-11-05**: Removing the default exception handler should now be done + via its ``disable()`` method: + + Before: + + unset($app['exception_handler']); + + After: + + $app['exception_handler']->disable(); + +* **2012-07-15**: removed the ``monolog.configure`` service. Use the + ``extend`` method instead: + + Before:: + + $app['monolog.configure'] = $app->protect(function ($monolog) use ($app) { + // do something + }); + + After:: + + $app['monolog'] = $app->share($app->extend('monolog', function($monolog, $app) { + // do something + + return $monolog; + })); + + +* **2012-06-17**: ``ControllerCollection`` now takes a required route instance + as a constructor argument. + + Before:: + + $controllers = new ControllerCollection(); + + After:: + + $controllers = new ControllerCollection(new Route()); + + // or even better + $controllers = $app['controllers_factory']; + +* **2012-06-17**: added application traits for PHP 5.4 + +* **2012-06-16**: renamed ``request.default_locale`` to ``locale`` + +* **2012-06-16**: Removed the ``translator.loader`` service. See documentation + for how to use XLIFF or YAML-based translation files. + +* **2012-06-15**: removed the ``twig.configure`` service. Use the ``extend`` + method instead: + + Before:: + + $app['twig.configure'] = $app->protect(function ($twig) use ($app) { + // do something + }); + + After:: + + $app['twig'] = $app->share($app->extend('twig', function($twig, $app) { + // do something + + return $twig; + })); + +* **2012-06-13**: Added a route ``before`` middleware + +* **2012-06-13**: Renamed the route ``middleware`` to ``before`` + +* **2012-06-13**: Added an extension for the Symfony Security component + +* **2012-05-31**: Made the ``BrowserKit``, ``CssSelector``, ``DomCrawler``, + ``Finder`` and ``Process`` components optional dependencies. Projects that + depend on them (e.g. through functional tests) should add those dependencies + to their ``composer.json``. + +* **2012-05-26**: added ``boot()`` to ``ServiceProviderInterface``. + +* **2012-05-26**: Removed ``SymfonyBridgesServiceProvider``. It is now implicit + by checking the existence of the bridge. + +* **2012-05-26**: Removed the ``translator.messages`` parameter (use + ``translator.domains`` instead). + +* **2012-05-24**: Removed the ``autoloader`` service (use composer instead). + The ``*.class_path`` settings on all the built-in providers have also been + removed in favor of Composer. + +* **2012-05-21**: Changed error() to allow handling specific exceptions. + +* **2012-05-20**: Added a way to define settings on a controller collection. + +* **2012-05-20**: The Request instance is not available anymore from the + Application after it has been handled. + +* **2012-04-01**: Added ``finish`` filters. + +* **2012-03-20**: Added ``json`` helper:: + + $data = array('some' => 'data'); + $response = $app->json($data); + +* **2012-03-11**: Added route middlewares. + +* **2012-03-02**: Switched to use Composer for dependency management. + +* **2012-02-27**: Updated to Symfony 2.1 session handling. + +* **2012-01-02**: Introduced support for streaming responses. + +* **2011-09-22**: ``ExtensionInterface`` has been renamed to + ``ServiceProviderInterface``. All built-in extensions have been renamed + accordingly (for instance, ``Silex\Extension\TwigExtension`` has been + renamed to ``Silex\Provider\TwigServiceProvider``). + +* **2011-09-22**: The way reusable applications work has changed. The + ``mount()`` method now takes an instance of ``ControllerCollection`` instead + of an ``Application`` one. + + Before:: + + $app = new Application(); + $app->get('/bar', function() { return 'foo'; }); + + return $app; + + After:: + + $app = new ControllerCollection(); + $app->get('/bar', function() { return 'foo'; }); + + return $app; + +* **2011-08-08**: The controller method configuration is now done on the Controller itself + + Before:: + + $app->match('/', function () { echo 'foo'; }, 'GET|POST'); + + After:: + + $app->match('/', function () { echo 'foo'; })->method('GET|POST'); diff --git a/vendor/silex/silex/doc/conf.py b/vendor/silex/silex/doc/conf.py new file mode 100644 index 00000000..dfe355c7 --- /dev/null +++ b/vendor/silex/silex/doc/conf.py @@ -0,0 +1,17 @@ +import sys, os +from sphinx.highlighting import lexers +from pygments.lexers.web import PhpLexer + +sys.path.append(os.path.abspath('_exts')) + +extensions = [] +master_doc = 'index' +highlight_language = 'php' + +project = u'Silex' +copyright = u'2010 Fabien Potencier' + +version = '0' +release = '0.0.0' + +lexers['php'] = PhpLexer(startinline=True) diff --git a/vendor/silex/silex/doc/contributing.rst b/vendor/silex/silex/doc/contributing.rst new file mode 100644 index 00000000..34a339d8 --- /dev/null +++ b/vendor/silex/silex/doc/contributing.rst @@ -0,0 +1,34 @@ +Contributing +============ + +We are open to contributions to the Silex code. If you find a bug or want to +contribute a provider, just follow these steps: + +* Fork `the Silex repository `_; + +* Make your feature addition or bug fix; + +* Add tests for it; + +* Optionally, add some documentation; + +* `Send a pull request + `_, to the correct + target branch (1.3 for bug fixes, master for new features). + +.. note:: + + Any code you contribute must be licensed under the MIT + License. + +Writing Documentation +===================== + +The documentation is written in `reStructuredText +`_ and can be generated using `sphinx +`_. + +.. code-block:: bash + + $ cd doc + $ sphinx-build -b html . build diff --git a/vendor/silex/silex/doc/cookbook/error_handler.rst b/vendor/silex/silex/doc/cookbook/error_handler.rst new file mode 100644 index 00000000..235c263a --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/error_handler.rst @@ -0,0 +1,38 @@ +Converting Errors to Exceptions +=============================== + +Silex catches exceptions that are thrown from within a request/response cycle. +However, it does *not* catch PHP errors and notices. This recipe tells you how +to catch them by converting them to exceptions. + +Registering the ErrorHandler +---------------------------- + +The ``Symfony/Debug`` package has an ``ErrorHandler`` class that solves this +problem. It converts all errors to exceptions, and exceptions are then caught +by Silex. + +Register it by calling the static ``register`` method:: + + use Symfony\Component\Debug\ErrorHandler; + + ErrorHandler::register(); + +It is recommended that you do this as early as possible. + +Handling fatal errors +--------------------- + +To handle fatal errors, you can additionally register a global +``ExceptionHandler``:: + + use Symfony\Component\Debug\ExceptionHandler; + + ExceptionHandler::register(); + +In production you may want to disable the debug output by passing ``false`` as +the ``$debug`` argument:: + + use Symfony\Component\Debug\ExceptionHandler; + + ExceptionHandler::register(false); diff --git a/vendor/silex/silex/doc/cookbook/form_no_csrf.rst b/vendor/silex/silex/doc/cookbook/form_no_csrf.rst new file mode 100644 index 00000000..e9bf595e --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/form_no_csrf.rst @@ -0,0 +1,36 @@ +Disabling CSRF Protection on a Form using the FormExtension +=========================================================== + +The *FormExtension* provides a service for building form in your application +with the Symfony Form component. When the :doc:`CSRF Service Provider +` is registered, the *FormExtension* uses the CSRF Protection +avoiding Cross-site request forgery, a method by which a malicious user +attempts to make your legitimate users unknowingly submit data that they don't +intend to submit. + +You can find more details about CSRF Protection and CSRF token in the +`Symfony Book +`_. + +In some cases (for example, when embedding a form in an html email) you might +want not to use this protection. The easiest way to avoid this is to +understand that it is possible to give specific options to your form builder +through the ``createBuilder()`` function. + +Example +------- + +.. code-block:: php + + $form = $app['form.factory']->createBuilder('form', null, array('csrf_protection' => false)); + +That's it, your form could be submitted from everywhere without CSRF Protection. + +Going further +------------- + +This specific example showed how to change the ``csrf_protection`` in the +``$options`` parameter of the ``createBuilder()`` function. More of them could +be passed through this parameter, it is as simple as using the Symfony +``getDefaultOptions()`` method in your form classes. `See more here +`_. diff --git a/vendor/silex/silex/doc/cookbook/guard_authentication.rst b/vendor/silex/silex/doc/cookbook/guard_authentication.rst new file mode 100644 index 00000000..8774f686 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/guard_authentication.rst @@ -0,0 +1,183 @@ +How to Create a Custom Authentication System with Guard +======================================================= + +Whether you need to build a traditional login form, an API token +authentication system or you need to integrate with some proprietary +single-sign-on system, the Guard component can make it easy... and fun! + +In this example, you'll build an API token authentication system and +learn how to work with Guard. + +Step 1) Create the Authenticator Class +-------------------------------------- + +Suppose you have an API where your clients will send an X-AUTH-TOKEN +header on each request. This token is composed of the username followed +by a password, separated by a colon (e.g. ``X-AUTH-TOKEN: coolguy:awesomepassword``). +Your job is to read this, find the associated user (if any) and check +the password. + +To create a custom authentication system, just create a class and make +it implement GuardAuthenticatorInterface. Or, extend the simpler +AbstractGuardAuthenticator. This requires you to implement six methods: + +.. code-block:: php + + encoderFactory = $encoderFactory; + } + + public function getCredentials(Request $request) + { + // Checks if the credential header is provided + if (!$token = $request->headers->get('X-AUTH-TOKEN')) { + return; + } + + // Parse the header or ignore it if the format is incorrect. + if (false === strpos($token, ':')) { + return; + } + list($username, $secret) = explode(':', $token, 2); + + return array( + 'username' => $username, + 'secret' => $secret, + ); + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + return $userProvider->loadUserByUsername($credentials['username']); + } + + public function checkCredentials($credentials, UserInterface $user) + { + // check credentials - e.g. make sure the password is valid + // return true to cause authentication success + + $encoder = $this->encoderFactory->getEncoder($user); + + return $encoder->isPasswordValid( + $user->getPassword(), + $credentials['secret'], + $user->getSalt() + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + // on success, let the request continue + return; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + { + $data = array( + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()), + + // or to translate this message + // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData()) + ); + + return new JsonResponse($data, 403); + } + + /** + * Called when authentication is needed, but it's not sent + */ + public function start(Request $request, AuthenticationException $authException = null) + { + $data = array( + // you might translate this message + 'message' => 'Authentication Required', + ); + + return new JsonResponse($data, 401); + } + + public function supportsRememberMe() + { + return false; + } + } + + +Step 2) Configure the Authenticator +----------------------------------- + +To finish this, register the class as a service: + +.. code-block:: php + + $app['app.token_authenticator'] = function ($app) { + return new App\Security\TokenAuthenticator($app['security.encoder_factory']); + }; + + +Finally, configure your `security.firewalls` key to use this authenticator: + +.. code-block:: php + + $app['security.firewalls'] = array( + 'main' => array( + 'guard' => array( + 'authenticators' => array( + 'app.token_authenticator' + ), + + // Using more than 1 authenticator, you must specify + // which one is used as entry point. + // 'entry_point' => 'app.token_authenticator', + ), + // configure where your users come from. Hardcode them, or load them from somewhere + // http://silex.sensiolabs.org/doc/providers/security.html#defining-a-custom-user-provider + 'users' => array( + //raw password = foo + 'victoria' => array('ROLE_USER', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + // 'anonymous' => true + ), + ); + +.. note:: + You can use many authenticators, they are executed by the order + they are configured. + +You did it! You now have a fully-working API token authentication +system. If your homepage required ROLE_USER, then you could test it +under different conditions: + +.. code-block:: bash + + # test with no token + curl http://localhost:8000/ + # {"message":"Authentication Required"} + + # test with a bad token + curl -H "X-AUTH-TOKEN: alan" http://localhost:8000/ + # {"message":"Username could not be found."} + + # test with a working token + curl -H "X-AUTH-TOKEN: victoria:foo" http://localhost:8000/ + # the homepage controller is executed: the page loads normally + +For more details read the Symfony cookbook entry on +`How to Create a Custom Authentication System with Guard `_. diff --git a/vendor/silex/silex/doc/cookbook/index.rst b/vendor/silex/silex/doc/cookbook/index.rst new file mode 100644 index 00000000..53b10fe2 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/index.rst @@ -0,0 +1,40 @@ +Cookbook +======== + +The cookbook section contains recipes for solving specific problems. + +.. toctree:: + :maxdepth: 1 + :hidden: + + json_request_body + session_storage + form_no_csrf + validator_yaml + sub_requests + error_handler + multiple_loggers + guard_authentication + +Recipes +------- + +* :doc:`Accepting a JSON Request Body ` A common need when + building a restful API is the ability to accept a JSON encoded entity from + the request body. + +* :doc:`Using PdoSessionStorage to store Sessions in the Database + `. + +* :doc:`Disabling the CSRF Protection on a Form using the FormExtension + `. + +* :doc:`Using YAML to configure Validation `. + +* :doc:`Making sub-Requests `. + +* :doc:`Converting Errors to Exceptions `. + +* :doc:`Using multiple Monolog Loggers `. + +* :doc:`How to Create a Custom Authentication System with Guard `. diff --git a/vendor/silex/silex/doc/cookbook/json_request_body.rst b/vendor/silex/silex/doc/cookbook/json_request_body.rst new file mode 100644 index 00000000..47159008 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/json_request_body.rst @@ -0,0 +1,95 @@ +Accepting a JSON Request Body +============================= + +A common need when building a restful API is the ability to accept a JSON +encoded entity from the request body. + +An example for such an API could be a blog post creation. + +Example API +----------- + +In this example we will create an API for creating a blog post. The following +is a spec of how we want it to work. + +Request +~~~~~~~ + +In the request we send the data for the blog post as a JSON object. We also +indicate that using the ``Content-Type`` header: + +.. code-block:: text + + POST /blog/posts + Accept: application/json + Content-Type: application/json + Content-Length: 57 + + {"title":"Hello World!","body":"This is my first post!"} + +Response +~~~~~~~~ + +The server responds with a 201 status code, telling us that the post was +created. It tells us the ``Content-Type`` of the response, which is also +JSON: + +.. code-block:: text + + HTTP/1.1 201 Created + Content-Type: application/json + Content-Length: 65 + Connection: close + + {"id":"1","title":"Hello World!","body":"This is my first post!"} + +Parsing the request body +------------------------ + +The request body should only be parsed as JSON if the ``Content-Type`` header +begins with ``application/json``. Since we want to do this for every request, +the easiest solution is to use an application before middleware. + +We simply use ``json_decode`` to parse the content of the request and then +replace the request data on the ``$request`` object:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\ParameterBag; + + $app->before(function (Request $request) { + if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) { + $data = json_decode($request->getContent(), true); + $request->request->replace(is_array($data) ? $data : array()); + } + }); + +Controller implementation +------------------------- + +Our controller will create a new blog post from the data provided and will +return the post object, including its ``id``, as JSON:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app->post('/blog/posts', function (Request $request) use ($app) { + $post = array( + 'title' => $request->request->get('title'), + 'body' => $request->request->get('body'), + ); + + $post['id'] = createPost($post); + + return $app->json($post, 201); + }); + +Manual testing +-------------- + +In order to manually test our API, we can use the ``curl`` command line +utility, which allows sending HTTP requests: + +.. code-block:: bash + + $ curl http://blog.lo/blog/posts -d '{"title":"Hello World!","body":"This is my first post!"}' -H 'Content-Type: application/json' + {"id":"1","title":"Hello World!","body":"This is my first post!"} diff --git a/vendor/silex/silex/doc/cookbook/multiple_loggers.rst b/vendor/silex/silex/doc/cookbook/multiple_loggers.rst new file mode 100644 index 00000000..cb103953 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/multiple_loggers.rst @@ -0,0 +1,69 @@ +Using multiple Monolog Loggers +============================== + +Having separate instances of Monolog for different parts of your system is +often desirable and allows you to configure them independently, allowing for fine +grained control of where your logging goes and in what detail. + +This simple example allows you to quickly configure several monolog instances, +using the bundled handler, but each with a different channel. + +.. code-block:: php + + $app['monolog.factory'] = $app->protect(function ($name) use ($app) { + $log = new $app['monolog.logger.class']($name); + $log->pushHandler($app['monolog.handler']); + + return $log; + }); + + foreach (array('auth', 'payments', 'stats') as $channel) { + $app['monolog.'.$channel] = function ($app) use ($channel) { + return $app['monolog.factory']($channel); + }; + } + +As your application grows, or your logging needs for certain areas of the +system become apparent, it should be straightforward to then configure that +particular service separately, including your customizations. + +.. code-block:: php + + use Monolog\Handler\StreamHandler; + + $app['monolog.payments'] = function ($app) { + $log = new $app['monolog.logger.class']('payments'); + $handler = new StreamHandler($app['monolog.payments.logfile'], $app['monolog.payment.level']); + $log->pushHandler($handler); + + return $log; + }; + +Alternatively, you could attempt to make the factory more complicated, and rely +on some conventions, such as checking for an array of handlers registered with +the container with the channel name, defaulting to the bundled handler. + +.. code-block:: php + + use Monolog\Handler\StreamHandler; + use Monolog\Logger; + + $app['monolog.factory'] = $app->protect(function ($name) use ($app) { + $log = new $app['monolog.logger.class']($name); + + $handlers = isset($app['monolog.'.$name.'.handlers']) + ? $app['monolog.'.$name.'.handlers'] + : array($app['monolog.handler']); + + foreach ($handlers as $handler) { + $log->pushHandler($handler); + } + + return $log; + }); + + $app['monolog.payments.handlers'] = function ($app) { + return array( + new StreamHandler(__DIR__.'/../payments.log', Logger::DEBUG), + ); + }; diff --git a/vendor/silex/silex/doc/cookbook/session_storage.rst b/vendor/silex/silex/doc/cookbook/session_storage.rst new file mode 100644 index 00000000..29328b49 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/session_storage.rst @@ -0,0 +1,89 @@ +Using PdoSessionStorage to store Sessions in the Database +========================================================= + +By default, the :doc:`SessionServiceProvider ` writes +session information in files using Symfony NativeFileSessionStorage. Most +medium to large websites use a database to store sessions instead of files, +because databases are easier to use and scale in a multi-webserver environment. + +Symfony's `NativeSessionStorage +`_ +has multiple storage handlers and one of them uses PDO to store sessions, +`PdoSessionHandler +`_. +To use it, replace the ``session.storage.handler`` service in your application +like explained below. + +With a dedicated PDO service +---------------------------- + +.. code-block:: php + + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + + $app->register(new Silex\Provider\SessionServiceProvider()); + + $app['pdo.dsn'] = 'mysql:dbname=mydatabase'; + $app['pdo.user'] = 'myuser'; + $app['pdo.password'] = 'mypassword'; + + $app['session.db_options'] = array( + 'db_table' => 'session', + 'db_id_col' => 'session_id', + 'db_data_col' => 'session_value', + 'db_time_col' => 'session_time', + ); + + $app['pdo'] = function () use ($app) { + return new PDO( + $app['pdo.dsn'], + $app['pdo.user'], + $app['pdo.password'] + ); + }; + + $app['session.storage.handler'] = function () use ($app) { + return new PdoSessionHandler( + $app['pdo'], + $app['session.db_options'] + ); + }; + +Using the DoctrineServiceProvider +--------------------------------- + +When using the :doc:`DoctrineServiceProvider ` You don't +have to make another database connection, simply pass the getWrappedConnection method. + +.. code-block:: php + + use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + + $app->register(new Silex\Provider\SessionServiceProvider()); + + $app['session.storage.handler'] = function () use ($app) { + return new PdoSessionHandler( + $app['db']->getWrappedConnection(), + array( + 'db_table' => 'session', + 'db_id_col' => 'session_id', + 'db_data_col' => 'session_value', + 'db_lifetime_col' => 'session_lifetime', + 'db_time_col' => 'session_time', + ) + ); + }; + +Database structure +------------------ + +PdoSessionStorage needs a database table with 3 columns: + +* ``session_id``: ID column (VARCHAR(255) or larger) +* ``session_value``: Value column (TEXT or CLOB) +* ``session_lifetime``: Lifetime column (INTEGER) +* ``session_time``: Time column (INTEGER) + +You can find examples of SQL statements to create the session table in the +`Symfony cookbook +`_ diff --git a/vendor/silex/silex/doc/cookbook/sub_requests.rst b/vendor/silex/silex/doc/cookbook/sub_requests.rst new file mode 100644 index 00000000..95d39136 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/sub_requests.rst @@ -0,0 +1,137 @@ +Making sub-Requests +=================== + +Since Silex is based on the ``HttpKernelInterface``, it allows you to simulate +requests against your application. This means that you can embed a page within +another, it also allows you to forward a request which is essentially an +internal redirect that does not change the URL. + +Basics +------ + +You can make a sub-request by calling the ``handle`` method on the +``Application``. This method takes three arguments: + +* ``$request``: An instance of the ``Request`` class which represents the + HTTP request. + +* ``$type``: Must be either ``HttpKernelInterface::MASTER_REQUEST`` or + ``HttpKernelInterface::SUB_REQUEST``. Certain listeners are only executed for + the master request, so it's important that this is set to ``SUB_REQUEST``. + +* ``$catch``: Catches exceptions and turns them into a response with status code + ``500``. This argument defaults to ``true``. For sub-requests you will most + likely want to set it to ``false``. + +By calling ``handle``, you can make a sub-request manually. Here's an example:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $subRequest = Request::create('/'); + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + +There's some more things that you need to keep in mind though. In most cases +you will want to forward some parts of the current master request to the +sub-request like cookies, server information, or the session. + +Here is a more advanced example that forwards said information (``$request`` +holds the master request):: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $subRequest = Request::create('/', 'GET', array(), $request->cookies->all(), array(), $request->server->all()); + if ($request->getSession()) { + $subRequest->setSession($request->getSession()); + } + + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + +To forward this response to the client, you can simply return it from a +controller:: + + use Silex\Application; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $app->get('/foo', function (Application $app, Request $request) { + $subRequest = Request::create('/', ...); + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + + return $response; + }); + +If you want to embed the response as part of a larger page you can call +``Response::getContent``:: + + $header = ...; + $footer = ...; + $body = $response->getContent(); + + return $header.$body.$footer; + +Rendering pages in Twig templates +--------------------------------- + +The :doc:`TwigServiceProvider ` provides a ``render`` +function that you can use in Twig templates. It gives you a convenient way to +embed pages. + +.. code-block:: jinja + + {{ render('/sidebar') }} + +For details, refer to the :doc:`TwigServiceProvider ` docs. + +Edge Side Includes +------------------ + +You can use ESI either through the :doc:`HttpCacheServiceProvider +` or a reverse proxy cache such as Varnish. This also +allows you to embed pages, however it also gives you the benefit of caching +parts of the page. + +Here is an example of how you would embed a page via ESI: + +.. code-block:: jinja + + + +For details, refer to the :doc:`HttpCacheServiceProvider +` docs. + +Dealing with the request base URL +--------------------------------- + +One thing to watch out for is the base URL. If your application is not +hosted at the webroot of your web server, then you may have an URL like +``http://example.org/foo/index.php/articles/42``. + +In this case, ``/foo/index.php`` is your request base path. Silex accounts for +this path prefix in the routing process, it reads it from +``$request->server``. In the context of sub-requests this can lead to issues, +because if you do not prepend the base path the request could mistake a part +of the path you want to match as the base path and cut it off. + +You can prevent that from happening by always prepending the base path when +constructing a request:: + + $url = $request->getUriForPath('/'); + $subRequest = Request::create($url, 'GET', array(), $request->cookies->all(), array(), $request->server->all()); + +This is something to be aware of when making sub-requests by hand. + +Services depending on the Request +--------------------------------- + +The container is a concept that is global to a Silex application, since the +application object **is** the container. Any request that is run against an +application will re-use the same set of services. Since these services are +mutable, code in a master request can affect the sub-requests and vice versa. +Any services depending on the ``request`` service will store the first request +that they get (could be master or sub-request), and keep using it, even if +that request is already over. + +Instead of injecting the ``request`` service, you should always inject the +``request_stack`` one instead. diff --git a/vendor/silex/silex/doc/cookbook/validator_yaml.rst b/vendor/silex/silex/doc/cookbook/validator_yaml.rst new file mode 100644 index 00000000..10a41fcf --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/validator_yaml.rst @@ -0,0 +1,35 @@ +Using YAML to configure Validation +================================== + +Simplicity is at the heart of Silex so there is no out of the box solution to +use YAML files for validation. But this doesn't mean that this is not +possible. Let's see how to do it. + +First, you need to install the YAML Component: + +.. code-block:: bash + + composer require symfony/yaml + +Next, you need to tell the Validation Service that you are not using +``StaticMethodLoader`` to load your class metadata but a YAML file:: + + $app->register(new ValidatorServiceProvider()); + + $app['validator.mapping.class_metadata_factory'] = new Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory( + new Symfony\Component\Validator\Mapping\Loader\YamlFileLoader(__DIR__.'/validation.yml') + ); + +Now, we can replace the usage of the static method and move all the validation +rules to ``validation.yml``: + +.. code-block:: yaml + + # validation.yml + Post: + properties: + title: + - NotNull: ~ + - NotBlank: ~ + body: + - Min: 100 diff --git a/vendor/silex/silex/doc/index.rst b/vendor/silex/silex/doc/index.rst new file mode 100644 index 00000000..d1a851d0 --- /dev/null +++ b/vendor/silex/silex/doc/index.rst @@ -0,0 +1,19 @@ +The Book +======== + +.. toctree:: + :maxdepth: 1 + + intro + usage + middlewares + organizing_controllers + services + providers + testing + cookbook/index + internals + contributing + providers/index + web_servers + changelog diff --git a/vendor/silex/silex/doc/internals.rst b/vendor/silex/silex/doc/internals.rst new file mode 100644 index 00000000..c7ffac8c --- /dev/null +++ b/vendor/silex/silex/doc/internals.rst @@ -0,0 +1,84 @@ +Internals +========= + +This chapter will tell you how Silex works internally. + +Silex +----- + +Application +~~~~~~~~~~~ + +The application is the main interface to Silex. It implements Symfony's +`HttpKernelInterface +`_, +so you can pass a `Request +`_ +to the ``handle`` method and it will return a `Response +`_. + +It extends the ``Pimple`` service container, allowing for flexibility on the +outside as well as the inside. You could replace any service, and you are also +able to read them. + +The application makes strong use of the `EventDispatcher +`_ to hook into the Symfony `HttpKernel +`_ +events. This allows fetching the ``Request``, converting string responses into +``Response`` objects and handling Exceptions. We also use it to dispatch some +custom events like before/after middlewares and errors. + +Controller +~~~~~~~~~~ + +The Symfony `Route +`_ is +actually quite powerful. Routes can be named, which allows for URL generation. +They can also have requirements for the variable parts. In order to allow +setting these through a nice interface, the ``match`` method (which is used by +``get``, ``post``, etc.) returns an instance of the ``Controller``, which +wraps a route. + +ControllerCollection +~~~~~~~~~~~~~~~~~~~~ + +One of the goals of exposing the `RouteCollection +`_ +was to make it mutable, so providers could add stuff to it. The challenge here +is the fact that routes know nothing about their name. The name only has +meaning in context of the ``RouteCollection`` and cannot be changed. + +To solve this challenge we came up with a staging area for routes. The +``ControllerCollection`` holds the controllers until ``flush`` is called, at +which point the routes are added to the ``RouteCollection``. Also, the +controllers are then frozen. This means that they can no longer be modified +and will throw an Exception if you try to do so. + +Unfortunately no good way for flushing implicitly could be found, which is why +flushing is now always explicit. The Application will flush, but if you want +to read the ``ControllerCollection`` before the request takes place, you will +have to call flush yourself. + +The ``Application`` provides a shortcut ``flush`` method for flushing the +``ControllerCollection``. + +.. tip:: + + Instead of creating an instance of ``RouteCollection`` yourself, use the + ``$app['controllers_factory']`` factory instead. + +Symfony +------- + +Following Symfony components are used by Silex: + +* **HttpFoundation**: For ``Request`` and ``Response``. + +* **HttpKernel**: Because we need a heart. + +* **Routing**: For matching defined routes. + +* **EventDispatcher**: For hooking into the HttpKernel. + +For more information, `check out the Symfony website `_. diff --git a/vendor/silex/silex/doc/intro.rst b/vendor/silex/silex/doc/intro.rst new file mode 100644 index 00000000..2ab2bc30 --- /dev/null +++ b/vendor/silex/silex/doc/intro.rst @@ -0,0 +1,50 @@ +Introduction +============ + +Silex is a PHP microframework. It is built on the shoulders of `Symfony`_ and +`Pimple`_ and also inspired by `Sinatra`_. + +Silex aims to be: + +* *Concise*: Silex exposes an intuitive and concise API. + +* *Extensible*: Silex has an extension system based around the Pimple + service-container that makes it easy to tie in third party libraries. + +* *Testable*: Silex uses Symfony's HttpKernel which abstracts request and + response. This makes it very easy to test apps and the framework itself. It + also respects the HTTP specification and encourages its proper use. + +In a nutshell, you define controllers and map them to routes, all in one step. + +Usage +----- + +.. code-block:: php + + get('/hello/{name}', function ($name) use ($app) { + return 'Hello '.$app->escape($name); + }); + + $app->run(); + +All that is needed to get access to the Framework is to include the +autoloader. + +Next, a route for ``/hello/{name}`` that matches for ``GET`` requests is +defined. When the route matches, the function is executed and the return value +is sent back to the client. + +Finally, the app is run. Visit ``/hello/world`` to see the result. It's really +that easy! + +.. _Symfony: http://symfony.com/ +.. _Pimple: http://pimple.sensiolabs.org/ +.. _Sinatra: http://www.sinatrarb.com/ diff --git a/vendor/silex/silex/doc/middlewares.rst b/vendor/silex/silex/doc/middlewares.rst new file mode 100644 index 00000000..c5c17cf0 --- /dev/null +++ b/vendor/silex/silex/doc/middlewares.rst @@ -0,0 +1,162 @@ +Middleware +========== + +Silex allows you to run code, that changes the default Silex behavior, at +different stages during the handling of a request through *middleware*: + +* *Application middleware* is triggered independently of the current handled + request; + +* *Route middleware* is triggered when its associated route is matched. + +Application Middleware +---------------------- + +Application middleware is only run for the "master" Request. + +Before Middleware +~~~~~~~~~~~~~~~~~ + +A *before* application middleware allows you to tweak the Request before the +controller is executed:: + + $app->before(function (Request $request, Application $app) { + // ... + }); + +By default, the middleware is run after the routing and the security. + +If you want your middleware to be run even if an exception is thrown early on +(on a 404 or 403 error for instance), then, you need to register it as an +early event:: + + $app->before(function (Request $request, Application $app) { + // ... + }, Application::EARLY_EVENT); + +In this case, the routing and the security won't have been executed, and so you +won't have access to the locale, the current route, or the security user. + +.. note:: + + The before middleware is an event registered on the Symfony *request* + event. + +After Middleware +~~~~~~~~~~~~~~~~ + +An *after* application middleware allows you to tweak the Response before it +is sent to the client:: + + $app->after(function (Request $request, Response $response) { + // ... + }); + +.. note:: + + The after middleware is an event registered on the Symfony *response* + event. + +Finish Middleware +~~~~~~~~~~~~~~~~~ + +A *finish* application middleware allows you to execute tasks after the +Response has been sent to the client (like sending emails or logging):: + + $app->finish(function (Request $request, Response $response) { + // ... + // Warning: modifications to the Request or Response will be ignored + }); + +.. note:: + + The finish middleware is an event registered on the Symfony *terminate* + event. + +Route Middleware +---------------- + +Route middleware is added to routes or route collections and it is only +triggered when the corresponding route is matched. You can also stack them:: + + $app->get('/somewhere', function () { + // ... + }) + ->before($before1) + ->before($before2) + ->after($after1) + ->after($after2) + ; + +Before Middleware +~~~~~~~~~~~~~~~~~ + +A *before* route middleware is fired just before the route callback, but after +the *before* application middleware:: + + $before = function (Request $request, Application $app) { + // ... + }; + + $app->get('/somewhere', function () { + // ... + }) + ->before($before); + +After Middleware +~~~~~~~~~~~~~~~~ + +An *after* route middleware is fired just after the route callback, but before +the application *after* application middleware:: + + $after = function (Request $request, Response $response, Application $app) { + // ... + }; + + $app->get('/somewhere', function () { + // ... + }) + ->after($after); + +Middleware Priority +------------------- + +You can add as much middleware as you want, in which case they are triggered +in the same order as you added them. + +You can explicitly control the priority of your middleware by passing an +additional argument to the registration methods:: + + $app->before(function (Request $request) { + // ... + }, 32); + +As a convenience, two constants allow you to register an event as early as +possible or as late as possible:: + + $app->before(function (Request $request) { + // ... + }, Application::EARLY_EVENT); + + $app->before(function (Request $request) { + // ... + }, Application::LATE_EVENT); + +Short-circuiting the Controller +------------------------------- + +If a *before* middleware returns a ``Response`` object, the request handling is +short-circuited (the next middleware won't be run, nor the route +callback), and the Response is passed to the *after* middleware right away:: + + $app->before(function (Request $request) { + // redirect the user to the login screen if access to the Resource is protected + if (...) { + return new RedirectResponse('/login'); + } + }); + +.. note:: + + A ``RuntimeException`` is thrown if a before middleware does not return a + Response or ``null``. diff --git a/vendor/silex/silex/doc/organizing_controllers.rst b/vendor/silex/silex/doc/organizing_controllers.rst new file mode 100644 index 00000000..50558cbb --- /dev/null +++ b/vendor/silex/silex/doc/organizing_controllers.rst @@ -0,0 +1,84 @@ +Organizing Controllers +====================== + +When your application starts to define too many controllers, you might want to +group them logically:: + + // define controllers for a blog + $blog = $app['controllers_factory']; + $blog->get('/', function () { + return 'Blog home page'; + }); + // ... + + // define controllers for a forum + $forum = $app['controllers_factory']; + $forum->get('/', function () { + return 'Forum home page'; + }); + + // define "global" controllers + $app->get('/', function () { + return 'Main home page'; + }); + + $app->mount('/blog', $blog); + $app->mount('/forum', $forum); + + // define controllers for a admin + $app->mount('/admin', function ($admin) { + // recursively mount + $admin->mount('/blog', function ($user) { + $user->get('/', function () { + return 'Admin Blog home page'; + }); + }); + }); + +.. note:: + + ``$app['controllers_factory']`` is a factory that returns a new instance + of ``ControllerCollection`` when used. + +``mount()`` prefixes all routes with the given prefix and merges them into the +main Application. So, ``/`` will map to the main home page, ``/blog/`` to the +blog home page, ``/forum/`` to the forum home page, and ``/admin/blog/`` to the +admin blog home page. + +.. caution:: + + When mounting a route collection under ``/blog``, it is not possible to + define a route for the ``/blog`` URL. The shortest possible URL is + ``/blog/``. + +.. note:: + + When calling ``get()``, ``match()``, or any other HTTP methods on the + Application, you are in fact calling them on a default instance of + ``ControllerCollection`` (stored in ``$app['controllers']``). + +Another benefit is the ability to apply settings on a set of controllers very +easily. Building on the example from the middleware section, here is how you +would secure all controllers for the backend collection:: + + $backend = $app['controllers_factory']; + + // ensure that all controllers require logged-in users + $backend->before($mustBeLogged); + +.. tip:: + + For a better readability, you can split each controller collection into a + separate file:: + + // blog.php + $blog = $app['controllers_factory']; + $blog->get('/', function () { return 'Blog home page'; }); + + return $blog; + + // app.php + $app->mount('/blog', include 'blog.php'); + + Instead of requiring a file, you can also create a :ref:`Controller + provider `. diff --git a/vendor/silex/silex/doc/providers.rst b/vendor/silex/silex/doc/providers.rst new file mode 100644 index 00000000..c3d049db --- /dev/null +++ b/vendor/silex/silex/doc/providers.rst @@ -0,0 +1,262 @@ +Providers +========= + +Providers allow the developer to reuse parts of an application into another +one. Silex provides two types of providers defined by two interfaces: +``ServiceProviderInterface`` for services and ``ControllerProviderInterface`` +for controllers. + +Service Providers +----------------- + +Loading providers +~~~~~~~~~~~~~~~~~ + +In order to load and use a service provider, you must register it on the +application:: + + $app = new Silex\Application(); + + $app->register(new Acme\DatabaseServiceProvider()); + +You can also provide some parameters as a second argument. These will be set +**after** the provider is registered, but **before** it is booted:: + + $app->register(new Acme\DatabaseServiceProvider(), array( + 'database.dsn' => 'mysql:host=localhost;dbname=myapp', + 'database.user' => 'root', + 'database.password' => 'secret_root_password', + )); + +Conventions +~~~~~~~~~~~ + +You need to watch out in what order you do certain things when interacting +with providers. Just keep these rules in mind: + +* Overriding existing services must occur **after** the provider is + registered. + + *Reason: If the service already exists, the provider will overwrite it.* + +* You can set parameters any time **after** the provider is registered, but + **before** the service is accessed. + + *Reason: Providers can set default values for parameters. Just like with + services, the provider will overwrite existing values.* + +Included providers +~~~~~~~~~~~~~~~~~~ + +There are a few providers that you get out of the box. All of these are within +the ``Silex\Provider`` namespace: + +* :doc:`AssetServiceProvider ` +* :doc:`CsrfServiceProvider ` +* :doc:`DoctrineServiceProvider ` +* :doc:`FormServiceProvider ` +* :doc:`HttpCacheServiceProvider ` +* :doc:`HttpFragmentServiceProvider ` +* :doc:`LocaleServiceProvider ` +* :doc:`MonologServiceProvider ` +* :doc:`RememberMeServiceProvider ` +* :doc:`SecurityServiceProvider ` +* :doc:`SerializerServiceProvider ` +* :doc:`ServiceControllerServiceProvider ` +* :doc:`SessionServiceProvider ` +* :doc:`SwiftmailerServiceProvider ` +* :doc:`TranslationServiceProvider ` +* :doc:`TwigServiceProvider ` +* :doc:`ValidatorServiceProvider ` +* :doc:`VarDumperServiceProvider ` + +.. note:: + + The Silex core team maintains a `WebProfiler + `_ provider that helps debug + code in the development environment thanks to the Symfony web debug toolbar + and the Symfony profiler. + +Third party providers +~~~~~~~~~~~~~~~~~~~~~ + +Some service providers are developed by the community. Those third-party +providers are listed on `Silex' repository wiki +`_. + +You are encouraged to share yours. + +Creating a provider +~~~~~~~~~~~~~~~~~~~ + +Providers must implement the ``Pimple\ServiceProviderInterface``:: + + interface ServiceProviderInterface + { + public function register(Container $container); + } + +This is very straight forward, just create a new class that implements the +register method. In the ``register()`` method, you can define services on the +application which then may make use of other services and parameters. + +.. tip:: + + The ``Pimple\ServiceProviderInterface`` belongs to the Pimple package, so + take care to only use the API of ``Pimple\Container`` within your + ``register`` method. Not only is this a good practice due to the way Pimple + and Silex work, but may allow your provider to be used outside of Silex. + +Optionally, your service provider can implement the +``Silex\Api\BootableProviderInterface``. A bootable provider must +implement the ``boot()`` method, with which you can configure the application, just +before it handles a request:: + + interface BootableProviderInterface + { + function boot(Application $app); + } + +Another optional interface, is the ``Silex\Api\EventListenerProviderInterface``. +This interface contains the ``subscribe()`` method, which allows your provider to +subscribe event listener with Silex's EventDispatcher, just before it handles a +request:: + + interface EventListenerProviderInterface + { + function subscribe(Container $app, EventDispatcherInterface $dispatcher); + } + +Here is an example of such a provider:: + + namespace Acme; + + use Pimple\Container; + use Pimple\ServiceProviderInterface; + use Silex\Application; + use Silex\Api\BootableProviderInterface; + use Silex\Api\EventListenerProviderInterface; + use Symfony\Component\EventDispatcher\EventDispatcherInterface; + use Symfony\Component\HttpKernel\KernelEvents; + use Symfony\Component\HttpKernel\Event\FilterResponseEvent; + + class HelloServiceProvider implements ServiceProviderInterface, BootableProviderInterface, EventListenerProviderInterface + { + public function register(Container $app) + { + $app['hello'] = $app->protect(function ($name) use ($app) { + $default = $app['hello.default_name'] ? $app['hello.default_name'] : ''; + $name = $name ?: $default; + + return 'Hello '.$app->escape($name); + }); + } + + public function boot(Application $app) + { + // do something + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addListener(KernelEvents::REQUEST, function(FilterResponseEvent $event) use ($app) { + // do something + }); + } + } + +This class provides a ``hello`` service which is a protected closure. It takes +a ``name`` argument and will return ``hello.default_name`` if no name is +given. If the default is also missing, it will use an empty string. + +You can now use this provider as follows:: + + use Symfony\Component\HttpFoundation\Request; + + $app = new Silex\Application(); + + $app->register(new Acme\HelloServiceProvider(), array( + 'hello.default_name' => 'Igor', + )); + + $app->get('/hello', function (Request $request) use ($app) { + $name = $request->get('name'); + + return $app['hello']($name); + }); + +In this example we are getting the ``name`` parameter from the query string, +so the request path would have to be ``/hello?name=Fabien``. + +.. _controller-providers: + +Controller Providers +-------------------- + +Loading providers +~~~~~~~~~~~~~~~~~ + +In order to load and use a controller provider, you must "mount" its +controllers under a path:: + + $app = new Silex\Application(); + + $app->mount('/blog', new Acme\BlogControllerProvider()); + +All controllers defined by the provider will now be available under the +``/blog`` path. + +Creating a provider +~~~~~~~~~~~~~~~~~~~ + +Providers must implement the ``Silex\Api\ControllerProviderInterface``:: + + interface ControllerProviderInterface + { + public function connect(Application $app); + } + +Here is an example of such a provider:: + + namespace Acme; + + use Silex\Application; + use Silex\Api\ControllerProviderInterface; + + class HelloControllerProvider implements ControllerProviderInterface + { + public function connect(Application $app) + { + // creates a new controller based on the default route + $controllers = $app['controllers_factory']; + + $controllers->get('/', function (Application $app) { + return $app->redirect('/hello'); + }); + + return $controllers; + } + } + +The ``connect`` method must return an instance of ``ControllerCollection``. +``ControllerCollection`` is the class where all controller related methods are +defined (like ``get``, ``post``, ``match``, ...). + +.. tip:: + + The ``Application`` class acts in fact as a proxy for these methods. + +You can use this provider as follows:: + + $app = new Silex\Application(); + + $app->mount('/blog', new Acme\HelloControllerProvider()); + +In this example, the ``/blog/`` path now references the controller defined in +the provider. + +.. tip:: + + You can also define a provider that implements both the service and the + controller provider interface and package in the same class the services + needed to make your controllers work. diff --git a/vendor/silex/silex/doc/providers/asset.rst b/vendor/silex/silex/doc/providers/asset.rst new file mode 100644 index 00000000..72c3d703 --- /dev/null +++ b/vendor/silex/silex/doc/providers/asset.rst @@ -0,0 +1,67 @@ +Asset +===== + +The *AssetServiceProvider* provides a way to manage URL generation and +versioning of web assets such as CSS stylesheets, JavaScript files and image +files. + +Parameters +---------- + +* **assets.version**: Default version for assets. + +* **assets.format_version** (optional): Default format for assets. + +* **assets.named_packages** (optional): Named packages. Keys are the package + names and values the configuration (supported keys are ``version``, + ``version_format``, ``base_urls``, and ``base_path``). + +Services +-------- + +* **assets.packages**: The asset service. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\AssetServiceProvider(), array( + 'assets.version' => 'v1', + 'assets.version_format' => '%s?version=%s', + 'assets.named_packages' => array( + 'css' => array('version' => 'css2', 'base_path' => '/whatever-makes-sense'), + 'images' => array('base_urls' => array('https://img.example.com')), + ), + )); + +.. note:: + + Add the Symfony Asset Component as a dependency: + + .. code-block:: bash + + composer require symfony/asset + + If you want to use assets in your Twig templates, you must also install the + Symfony Twig Bridge: + + .. code-block:: bash + + composer require symfony/twig-bridge + +Usage +----- + +The AssetServiceProvider is mostly useful with the Twig provider: + +.. code-block:: jinja + + {{ asset('/css/foo.png') }} + {{ asset('/css/foo.css', 'css') }} + {{ asset('/img/foo.png', 'images') }} + + {{ asset_version('/css/foo.png') }} + +For more information, check out the `Asset Component documentation +`_. diff --git a/vendor/silex/silex/doc/providers/csrf.rst b/vendor/silex/silex/doc/providers/csrf.rst new file mode 100644 index 00000000..3bd35f4b --- /dev/null +++ b/vendor/silex/silex/doc/providers/csrf.rst @@ -0,0 +1,52 @@ +CSRF +==== + +The *CsrfServiceProvider* provides a service for building forms in your +application with the Symfony Form component. + +Parameters +---------- + +* none + +Services +-------- + +* **csrf.token_manager**: An instance of an implementation of the + `CsrfProviderInterface + `_, + defaults to a `DefaultCsrfProvider + `_. + +Registering +----------- + +.. code-block:: php + + use Silex\Provider\CsrfServiceProvider; + + $app->register(new CsrfServiceProvider()); + +.. note:: + + Add the Symfony's `Security CSRF Component + `_ as a + dependency: + + .. code-block:: bash + + composer require symfony/security-csrf + +Usage +----- + +When the CSRF Service Provider is registered, all forms created via the Form +Service Provider are protected against CSRF by default. + +You can also use the CSRF protection even without using the Symfony Form +component. If, for example, you're doing a DELETE action, you can check the +CSRF token:: + + use Symfony\Component\Security\Csrf\CsrfToken; + + $app['csrf.token_manager']->isTokenValid(new CsrfToken('token_id', 'TOKEN')); diff --git a/vendor/silex/silex/doc/providers/doctrine.rst b/vendor/silex/silex/doc/providers/doctrine.rst new file mode 100644 index 00000000..0ef167b7 --- /dev/null +++ b/vendor/silex/silex/doc/providers/doctrine.rst @@ -0,0 +1,137 @@ +Doctrine +======== + +The *DoctrineServiceProvider* provides integration with the `Doctrine DBAL +`_ for easy database access +(Doctrine ORM integration is **not** supplied). + +Parameters +---------- + +* **db.options**: Array of Doctrine DBAL options. + + These options are available: + + * **driver**: The database driver to use, defaults to ``pdo_mysql``. + Can be any of: ``pdo_mysql``, ``pdo_sqlite``, ``pdo_pgsql``, + ``pdo_oci``, ``oci8``, ``ibm_db2``, ``pdo_ibm``, ``pdo_sqlsrv``. + + * **dbname**: The name of the database to connect to. + + * **host**: The host of the database to connect to. Defaults to + localhost. + + * **user**: The user of the database to connect to. Defaults to + root. + + * **password**: The password of the database to connect to. + + * **charset**: Only relevant for ``pdo_mysql``, and ``pdo_oci/oci8``, + specifies the charset used when connecting to the database. + + * **path**: Only relevant for ``pdo_sqlite``, specifies the path to + the SQLite database. + + * **port**: Only relevant for ``pdo_mysql``, ``pdo_pgsql``, and ``pdo_oci/oci8``, + specifies the port of the database to connect to. + + These and additional options are described in detail in the `Doctrine DBAL + configuration documentation `_. + +Services +-------- + +* **db**: The database connection, instance of + ``Doctrine\DBAL\Connection``. + +* **db.config**: Configuration object for Doctrine. Defaults to + an empty ``Doctrine\DBAL\Configuration``. + +* **db.event_manager**: Event Manager for Doctrine. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\DoctrineServiceProvider(), array( + 'db.options' => array( + 'driver' => 'pdo_sqlite', + 'path' => __DIR__.'/app.db', + ), + )); + +.. note:: + + Add the Doctrine DBAL as a dependency: + + .. code-block:: bash + + composer require "doctrine/dbal:~2.2" + +Usage +----- + +The Doctrine provider provides a ``db`` service. Here is a usage +example:: + + $app->get('/blog/{id}', function ($id) use ($app) { + $sql = "SELECT * FROM posts WHERE id = ?"; + $post = $app['db']->fetchAssoc($sql, array((int) $id)); + + return "

{$post['title']}

". + "

{$post['body']}

"; + }); + +Using multiple databases +------------------------ + +The Doctrine provider can allow access to multiple databases. In order to +configure the data sources, replace the **db.options** with **dbs.options**. +**dbs.options** is an array of configurations where keys are connection names +and values are options:: + + $app->register(new Silex\Provider\DoctrineServiceProvider(), array( + 'dbs.options' => array ( + 'mysql_read' => array( + 'driver' => 'pdo_mysql', + 'host' => 'mysql_read.someplace.tld', + 'dbname' => 'my_database', + 'user' => 'my_username', + 'password' => 'my_password', + 'charset' => 'utf8mb4', + ), + 'mysql_write' => array( + 'driver' => 'pdo_mysql', + 'host' => 'mysql_write.someplace.tld', + 'dbname' => 'my_database', + 'user' => 'my_username', + 'password' => 'my_password', + 'charset' => 'utf8mb4', + ), + ), + )); + +The first registered connection is the default and can simply be accessed as +you would if there was only one connection. Given the above configuration, +these two lines are equivalent:: + + $app['db']->fetchAll('SELECT * FROM table'); + + $app['dbs']['mysql_read']->fetchAll('SELECT * FROM table'); + +Using multiple connections:: + + $app->get('/blog/{id}', function ($id) use ($app) { + $sql = "SELECT * FROM posts WHERE id = ?"; + $post = $app['dbs']['mysql_read']->fetchAssoc($sql, array((int) $id)); + + $sql = "UPDATE posts SET value = ? WHERE id = ?"; + $app['dbs']['mysql_write']->executeUpdate($sql, array('newValue', (int) $id)); + + return "

{$post['title']}

". + "

{$post['body']}

"; + }); + +For more information, consult the `Doctrine DBAL documentation +`_. diff --git a/vendor/silex/silex/doc/providers/form.rst b/vendor/silex/silex/doc/providers/form.rst new file mode 100644 index 00000000..6818b858 --- /dev/null +++ b/vendor/silex/silex/doc/providers/form.rst @@ -0,0 +1,216 @@ +Form +==== + +The *FormServiceProvider* provides a service for building forms in +your application with the Symfony Form component. + +Parameters +---------- + +* none + +Services +-------- + +* **form.factory**: An instance of `FormFactory + `_, + that is used to build a form. + +Registering +----------- + +.. code-block:: php + + use Silex\Provider\FormServiceProvider; + + $app->register(new FormServiceProvider()); + +.. note:: + + If you don't want to create your own form layout, it's fine: a default one + will be used. But you will have to register the :doc:`translation provider + ` as the default form layout requires it:: + + $app->register(new Silex\Provider\TranslationServiceProvider(), array( + 'translator.domains' => array(), + )); + + If you want to use validation with forms, do not forget to register the + :doc:`Validator provider `. + +.. note:: + + Add the Symfony Form Component as a dependency: + + .. code-block:: bash + + composer require symfony/form + + If you are going to use the validation extension with forms, you must also + add a dependency to the ``symfony/validator`` and ``symfony/config`` + components: + + .. code-block:: bash + + composer require symfony/validator symfony/config + + If you want to use forms in your Twig templates, you can also install the + Symfony Twig Bridge. Make sure to install, if you didn't do that already, + the Translation component in order for the bridge to work: + + .. code-block:: bash + + composer require symfony/twig-bridge + +Usage +----- + +The FormServiceProvider provides a ``form.factory`` service. Here is a usage +example:: + + use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + use Symfony\Component\Form\Extension\Core\Type\FormType; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; + + $app->match('/form', function (Request $request) use ($app) { + // some default data for when the form is displayed the first time + $data = array( + 'name' => 'Your name', + 'email' => 'Your email', + ); + + $form = $app['form.factory']->createBuilder(FormType::class, $data) + ->add('name') + ->add('email') + ->add('billing_plan', ChoiceType::class, array( + 'choices' => array('free' => 1, 'small business' => 2, 'corporate' => 3), + 'expanded' => true, + )) + ->add('submit', SubmitType::class, [ + 'label' => 'Save', + ]) + ->getForm(); + + $form->handleRequest($request); + + if ($form->isValid()) { + $data = $form->getData(); + + // do something with the data + + // redirect somewhere + return $app->redirect('...'); + } + + // display the form + return $app['twig']->render('index.twig', array('form' => $form->createView())); + }); + +And here is the ``index.twig`` form template (requires ``symfony/twig-bridge``): + +.. code-block:: jinja + +
+ {{ form_widget(form) }} + + +
+ +If you are using the validator provider, you can also add validation to your +form by adding constraints on the fields:: + + use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + use Symfony\Component\Form\Extension\Core\Type\FormType; + use Symfony\Component\Form\Extension\Core\Type\SubmitType; + use Symfony\Component\Form\Extension\Core\Type\TextType; + use Symfony\Component\Validator\Constraints as Assert; + + $app->register(new Silex\Provider\ValidatorServiceProvider()); + $app->register(new Silex\Provider\TranslationServiceProvider(), array( + 'translator.domains' => array(), + )); + + $form = $app['form.factory']->createBuilder(FormType::class) + ->add('name', TextType::class, array( + 'constraints' => array(new Assert\NotBlank(), new Assert\Length(array('min' => 5))) + )) + ->add('email', TextType::class, array( + 'constraints' => new Assert\Email() + )) + ->add('billing_plan', ChoiceType::class, array( + 'choices' => array('free' => 1, 'small business' => 2, 'corporate' => 3), + 'expanded' => true, + 'constraints' => new Assert\Choice(array(1, 2, 3)), + )) + ->add('submit', SubmitType::class, [ + 'label' => 'Save', + ]) + ->getForm(); + +You can register form types by extending ``form.types``:: + + $app['your.type.service'] = function ($app) { + return new YourServiceFormType(); + }; + $app->extend('form.types', function ($types) use ($app) { + $types[] = new YourFormType(); + $types[] = 'your.type.service'; + + return $types; + })); + +You can register form extensions by extending ``form.extensions``:: + + $app->extend('form.extensions', function ($extensions) use ($app) { + $extensions[] = new YourTopFormExtension(); + + return $extensions; + }); + + +You can register form type extensions by extending ``form.type.extensions``:: + + $app['your.type.extension.service'] = function ($app) { + return new YourServiceFormTypeExtension(); + }; + $app->extend('form.type.extensions', function ($extensions) use ($app) { + $extensions[] = new YourFormTypeExtension(); + $extensions[] = 'your.type.extension.service'; + + return $extensions; + }); + +You can register form type guessers by extending ``form.type.guessers``:: + + $app['your.type.guesser.service'] = function ($app) { + return new YourServiceFormTypeGuesser(); + }; + $app->extend('form.type.guessers', function ($guessers) use ($app) { + $guessers[] = new YourFormTypeGuesser(); + $guessers[] = 'your.type.guesser.service'; + + return $guessers; + }); + +.. warning:: + + CSRF protection is only available and automatically enabled when the + :doc:`CSRF Service Provider
` is registered. + +Traits +------ + +``Silex\Application\FormTrait`` adds the following shortcuts: + +* **form**: Creates a FormBuilderInterface instance. + +* **namedForm**: Creates a FormBuilderInterface instance (named). + +.. code-block:: php + + $app->form($data); + + $app->namedForm($name, $data, $options, $type); + +For more information, consult the `Symfony Forms documentation +`_. diff --git a/vendor/silex/silex/doc/providers/http_cache.rst b/vendor/silex/silex/doc/providers/http_cache.rst new file mode 100644 index 00000000..8bc98f67 --- /dev/null +++ b/vendor/silex/silex/doc/providers/http_cache.rst @@ -0,0 +1,128 @@ +HTTP Cache +========== + +The *HttpCacheServiceProvider* provides support for the Symfony Reverse +Proxy. + +Parameters +---------- + +* **http_cache.cache_dir**: The cache directory to store the HTTP cache data. + +* **http_cache.options** (optional): An array of options for the `HttpCache + `_ + constructor. + +Services +-------- + +* **http_cache**: An instance of `HttpCache + `_. + +* **http_cache.esi**: An instance of `Esi + `_, + that implements the ESI capabilities to Request and Response instances. + +* **http_cache.store**: An instance of `Store + `_, + that implements all the logic for storing cache metadata (Request and Response + headers). + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => __DIR__.'/cache/', + )); + +Usage +----- + +Silex already supports any reverse proxy like Varnish out of the box by +setting Response HTTP cache headers:: + + use Symfony\Component\HttpFoundation\Response; + + $app->get('/', function() { + return new Response('Foo', 200, array( + 'Cache-Control' => 's-maxage=5', + )); + }); + +.. tip:: + + If you want Silex to trust the ``X-Forwarded-For*`` headers from your + reverse proxy at address $ip, you will need to whitelist it as documented + in `Trusting Proxies + `_. + + If you would be running Varnish in front of your application on the same machine:: + + use Symfony\Component\HttpFoundation\Request; + + Request::setTrustedProxies(array('127.0.0.1', '::1')); + $app->run(); + +This provider allows you to use the Symfony reverse proxy natively with +Silex applications by using the ``http_cache`` service. The Symfony reverse proxy +acts much like any other proxy would, so you will want to whitelist it:: + + use Symfony\Component\HttpFoundation\Request; + + Request::setTrustedProxies(array('127.0.0.1')); + $app['http_cache']->run(); + +The provider also provides ESI support:: + + $app->get('/', function() { + $response = new Response(<< + + Hello + + + + + EOF + , 200, array( + 'Surrogate-Control' => 'content="ESI/1.0"', + )); + + $response->setTtl(20); + + return $response; + }); + + $app->get('/included', function() { + $response = new Response('Foo'); + $response->setTtl(5); + + return $response; + }); + + $app['http_cache']->run(); + +If your application doesn't use ESI, you can disable it to slightly improve the +overall performance:: + + $app->register(new Silex\Provider\HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => __DIR__.'/cache/', + 'http_cache.esi' => null, + )); + +.. tip:: + + To help you debug caching issues, set your application ``debug`` to true. + Symfony automatically adds a ``X-Symfony-Cache`` header to each response + with useful information about cache hits and misses. + + If you are *not* using the Symfony Session provider, you might want to set + the PHP ``session.cache_limiter`` setting to an empty value to avoid the + default PHP behavior. + + Finally, check that your Web server does not override your caching strategy. + +For more information, consult the `Symfony HTTP Cache documentation +`_. diff --git a/vendor/silex/silex/doc/providers/http_fragment.rst b/vendor/silex/silex/doc/providers/http_fragment.rst new file mode 100644 index 00000000..8e681853 --- /dev/null +++ b/vendor/silex/silex/doc/providers/http_fragment.rst @@ -0,0 +1,70 @@ +HTTP Fragment +============= + +The *HttpFragmentServiceProvider* provides support for the Symfony fragment +sub-framework, which allows you to embed fragments of HTML in a template. + +Parameters +---------- + +* **fragment.path**: The path to use for the URL generated for ESI and + HInclude URLs (``/_fragment`` by default). + +* **uri_signer.secret**: The secret to use for the URI signer service (used + for the HInclude renderer). + +* **fragment.renderers.hinclude.global_template**: The content or Twig + template to use for the default content when using the HInclude renderer. + +Services +-------- + +* **fragment.handler**: An instance of `FragmentHandler + `_. + +* **fragment.renderers**: An array of fragment renderers (by default, the + inline, ESI, and HInclude renderers are pre-configured). + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\HttpFragmentServiceProvider()); + +Usage +----- + +.. note:: + + This section assumes that you are using Twig for your templates. + +Instead of building a page out of a single request/controller/template, the +fragment framework allows you to build a page from several +controllers/sub-requests/sub-templates by using **fragments**. + +Including "sub-pages" in the main page can be done with the Twig ``render()`` +function: + +.. code-block:: jinja + + The main page content. + + {{ render('/foo') }} + + The main page content resumes here. + +The ``render()`` call is replaced by the content of the ``/foo`` URL +(internally, a sub-request is handled by Silex to render the sub-page). + +Instead of making internal sub-requests, you can also use the ESI (the +sub-request is handled by a reverse proxy) or the HInclude strategies (the +sub-request is handled by a web browser): + +.. code-block:: jinja + + {{ render(url('route_name')) }} + + {{ render_esi(url('route_name')) }} + + {{ render_hinclude(url('route_name')) }} diff --git a/vendor/silex/silex/doc/providers/index.rst b/vendor/silex/silex/doc/providers/index.rst new file mode 100644 index 00000000..8c5a1754 --- /dev/null +++ b/vendor/silex/silex/doc/providers/index.rst @@ -0,0 +1,24 @@ +Built-in Service Providers +========================== + +.. toctree:: + :maxdepth: 1 + + twig + asset + monolog + session + swiftmailer + locale + translation + validator + form + csrf + http_cache + http_fragment + security + remember_me + serializer + service_controller + var_dumper + doctrine diff --git a/vendor/silex/silex/doc/providers/locale.rst b/vendor/silex/silex/doc/providers/locale.rst new file mode 100644 index 00000000..8f6cd675 --- /dev/null +++ b/vendor/silex/silex/doc/providers/locale.rst @@ -0,0 +1,24 @@ +Locale +====== + +The *LocaleServiceProvider* manages the locale of an application. + +Parameters +---------- + +* **locale**: The locale of the user. When set before any request handling, it + defines the default locale (``en`` by default). When a request is being + handled, it is automatically set according to the ``_locale`` request + attribute of the current route. + +Services +-------- + +* n/a + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\LocaleServiceProvider()); diff --git a/vendor/silex/silex/doc/providers/monolog.rst b/vendor/silex/silex/doc/providers/monolog.rst new file mode 100644 index 00000000..645b7106 --- /dev/null +++ b/vendor/silex/silex/doc/providers/monolog.rst @@ -0,0 +1,115 @@ +Monolog +======= + +The *MonologServiceProvider* provides a default logging mechanism through +Jordi Boggiano's `Monolog `_ library. + +It will log requests and errors and allow you to add logging to your +application. This allows you to debug and monitor the behaviour, +even in production. + +Parameters +---------- + +* **monolog.logfile**: File where logs are written to. +* **monolog.bubble**: (optional) Whether the messages that are handled can bubble up the stack or not. +* **monolog.permission**: (optional) File permissions default (null), nothing change. + +* **monolog.level** (optional): Level of logging, defaults + to ``DEBUG``. Must be one of ``Logger::DEBUG``, ``Logger::INFO``, + ``Logger::WARNING``, ``Logger::ERROR``. ``DEBUG`` will log + everything, ``INFO`` will log everything except ``DEBUG``, + etc. + + In addition to the ``Logger::`` constants, it is also possible to supply the + level in string form, for example: ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, + ``"ERROR"``. + +* **monolog.name** (optional): Name of the monolog channel, + defaults to ``myapp``. + +* **monolog.exception.logger_filter** (optional): An anonymous function that + returns an error level for on uncaught exception that should be logged. + +* **monolog.use_error_handler** (optional): Whether errors and uncaught exceptions + should be handled by the Monolog ``ErrorHandler`` class and added to the log. + By default the error handler is enabled unless the application ``debug`` parameter + is set to true. + + Please note that enabling the error handler may silence some errors, + ignoring the PHP ``display_errors`` configuration setting. + +Services +-------- + +* **monolog**: The monolog logger instance. + + Example usage:: + + $app['monolog']->debug('Testing the Monolog logging.'); + +* **monolog.listener**: An event listener to log requests, responses and errors. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\MonologServiceProvider(), array( + 'monolog.logfile' => __DIR__.'/development.log', + )); + +.. note:: + + Add Monolog as a dependency: + + .. code-block:: bash + + composer require monolog/monolog + +Usage +----- + +The MonologServiceProvider provides a ``monolog`` service. You can use it to +add log entries for any logging level through ``debug()``, ``info()``, +``warning()`` and ``error()``:: + + use Symfony\Component\HttpFoundation\Response; + + $app->post('/user', function () use ($app) { + // ... + + $app['monolog']->info(sprintf("User '%s' registered.", $username)); + + return new Response('', 201); + }); + +Customization +------------- + +You can configure Monolog (like adding or changing the handlers) before using +it by extending the ``monolog`` service:: + + $app->extend('monolog', function($monolog, $app) { + $monolog->pushHandler(...); + + return $monolog; + }); + +By default, all requests, responses and errors are logged by an event listener +registered as a service called `monolog.listener`. You can replace or remove +this service if you want to modify or disable the logged information. + +Traits +------ + +``Silex\Application\MonologTrait`` adds the following shortcuts: + +* **log**: Logs a message. + +.. code-block:: php + + $app->log(sprintf("User '%s' registered.", $username)); + +For more information, check out the `Monolog documentation +`_. diff --git a/vendor/silex/silex/doc/providers/remember_me.rst b/vendor/silex/silex/doc/providers/remember_me.rst new file mode 100644 index 00000000..7fdaaaba --- /dev/null +++ b/vendor/silex/silex/doc/providers/remember_me.rst @@ -0,0 +1,69 @@ +Remember Me +=========== + +The *RememberMeServiceProvider* adds "Remember-Me" authentication to the +*SecurityServiceProvider*. + +Parameters +---------- + +n/a + +Services +-------- + +n/a + +.. note:: + + The service provider defines many other services that are used internally + but rarely need to be customized. + +Registering +----------- + +Before registering this service provider, you must register the +*SecurityServiceProvider*:: + + $app->register(new Silex\Provider\SecurityServiceProvider()); + $app->register(new Silex\Provider\RememberMeServiceProvider()); + + $app['security.firewalls'] = array( + 'my-firewall' => array( + 'pattern' => '^/secure$', + 'form' => true, + 'logout' => true, + 'remember_me' => array( + 'key' => 'Choose_A_Unique_Random_Key', + 'always_remember_me' => true, + /* Other options */ + ), + 'users' => array( /* ... */ ), + ), + ); + +Options +------- + +* **key**: A secret key to generate tokens (you should generate a random + string). + +* **name**: Cookie name (default: ``REMEMBERME``). + +* **lifetime**: Cookie lifetime (default: ``31536000`` ~ 1 year). + +* **path**: Cookie path (default: ``/``). + +* **domain**: Cookie domain (default: ``null`` = request domain). + +* **secure**: Cookie is secure (default: ``false``). + +* **httponly**: Cookie is HTTP only (default: ``true``). + +* **always_remember_me**: Enable remember me (default: ``false``). + +* **remember_me_parameter**: Name of the request parameter enabling remember_me + on login. To add the checkbox to the login form. You can find more + information in the `Symfony cookbook + `_ + (default: ``_remember_me``). diff --git a/vendor/silex/silex/doc/providers/security.rst b/vendor/silex/silex/doc/providers/security.rst new file mode 100644 index 00000000..f84d3180 --- /dev/null +++ b/vendor/silex/silex/doc/providers/security.rst @@ -0,0 +1,711 @@ +Security +======== + +The *SecurityServiceProvider* manages authentication and authorization for +your applications. + +Parameters +---------- + +* **security.hide_user_not_found** (optional): Defines whether to hide user not + found exception or not. Defaults to ``true``. + +* **security.encoder.bcrypt.cost** (optional): Defines BCrypt password encoder cost. Defaults to 13. + +Services +-------- + +* **security.token_storage**: Gives access to the user token. + +* **security.authorization_checker**: Allows to check authorizations for the + users. + +* **security.authentication_manager**: An instance of + `AuthenticationProviderManager + `_, + responsible for authentication. + +* **security.access_manager**: An instance of `AccessDecisionManager + `_, + responsible for authorization. + +* **security.session_strategy**: Define the session strategy used for + authentication (default to a migration strategy). + +* **security.user_checker**: Checks user flags after authentication. + +* **security.last_error**: Returns the last authentication errors when given a + Request object. + +* **security.encoder_factory**: Defines the encoding strategies for user + passwords (uses ``security.default_encoder``). + +* **security.default_encoder**: The encoder to use by default for all users (BCrypt). + +* **security.encoder.digest**: Digest password encoder. + +* **security.encoder.bcrypt**: BCrypt password encoder. + +* **security.encoder.pbkdf2**: Pbkdf2 password encoder. + +* **user**: Returns the current user + +.. note:: + + The service provider defines many other services that are used internally + but rarely need to be customized. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SecurityServiceProvider(), array( + 'security.firewalls' => // see below + )); + +.. note:: + + Add the Symfony Security Component as a dependency: + + .. code-block:: bash + + composer require symfony/security + +.. caution:: + + If you're using a form to authenticate users, you need to enable + ``SessionServiceProvider``. + +.. caution:: + + The security features are only available after the Application has been + booted. So, if you want to use it outside of the handling of a request, + don't forget to call ``boot()`` first:: + + $app->boot(); + +Usage +----- + +The Symfony Security component is powerful. To learn more about it, read the +`Symfony Security documentation +`_. + +.. tip:: + + When a security configuration does not behave as expected, enable logging + (with the Monolog extension for instance) as the Security Component logs a + lot of interesting information about what it does and why. + +Below is a list of recipes that cover some common use cases. + +Accessing the current User +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The current user information is stored in a token that is accessible via the +``security`` service:: + + $token = $app['security.token_storage']->getToken(); + +If there is no information about the user, the token is ``null``. If the user +is known, you can get it with a call to ``getUser()``:: + + if (null !== $token) { + $user = $token->getUser(); + } + +The user can be a string, an object with a ``__toString()`` method, or an +instance of `UserInterface +`_. + +Securing a Path with HTTP Authentication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following configuration uses HTTP basic authentication to secure URLs +under ``/admin/``:: + + $app['security.firewalls'] = array( + 'admin' => array( + 'pattern' => '^/admin', + 'http' => true, + 'users' => array( + // raw password is foo + 'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + ), + ); + +The ``pattern`` is a regular expression on the URL path; the ``http`` setting +tells the security layer to use HTTP basic authentication and the ``users`` +entry defines valid users. + +If you want to restrict the firewall by more than the URL pattern (like the +HTTP method, the client IP, the hostname, or any Request attributes), use an +instance of a `RequestMatcher +`_ +for the ``pattern`` option:: + + use Symfony/Component/HttpFoundation/RequestMatcher; + + $app['security.firewalls'] = array( + 'admin' => array( + 'pattern' => new RequestMatcher('^/admin', 'example.com', 'POST'), + // ... + ), + ); + +Each user is defined with the following information: + +* The role or an array of roles for the user (roles are strings beginning with + ``ROLE_`` and ending with anything you want); + +* The user encoded password. + +.. caution:: + + All users must at least have one role associated with them. + +The default configuration of the extension enforces encoded passwords. To +generate a valid encoded password from a raw password, use the +``security.encoder_factory`` service:: + + // find the encoder for a UserInterface instance + $encoder = $app['security.encoder_factory']->getEncoder($user); + + // compute the encoded password for foo + $password = $encoder->encodePassword('foo', $user->getSalt()); + +When the user is authenticated, the user stored in the token is an instance of +`User +`_ + +.. caution:: + + If you are using php-cgi under Apache, you need to add this configuration + to make things work correctly: + + .. code-block:: apache + + RewriteEngine On + RewriteCond %{HTTP:Authorization} ^(.+)$ + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ app.php [QSA,L] + +Securing a Path with a Form +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using a form to authenticate users is very similar to the above configuration. +Instead of using the ``http`` setting, use the ``form`` one and define these +two parameters: + +* **login_path**: The login path where the user is redirected when they are + accessing a secured area without being authenticated so that they can enter + their credentials; + +* **check_path**: The check URL used by Symfony to validate the credentials of + the user. + +Here is how to secure all URLs under ``/admin/`` with a form:: + + $app['security.firewalls'] = array( + 'admin' => array( + 'pattern' => '^/admin/', + 'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'), + 'users' => array( + 'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + ), + ); + +Always keep in mind the following two golden rules: + +* The ``login_path`` path must always be defined **outside** the secured area + (or if it is in the secured area, the ``anonymous`` authentication mechanism + must be enabled -- see below); + +* The ``check_path`` path must always be defined **inside** the secured area. + +For the login form to work, create a controller like the following:: + + use Symfony\Component\HttpFoundation\Request; + + $app->get('/login', function(Request $request) use ($app) { + return $app['twig']->render('login.html', array( + 'error' => $app['security.last_error']($request), + 'last_username' => $app['session']->get('_security.last_username'), + )); + }); + +The ``error`` and ``last_username`` variables contain the last authentication +error and the last username entered by the user in case of an authentication +error. + +Create the associated template: + +.. code-block:: jinja + +
+ {{ error }} + + + +
+ +.. note:: + + The ``admin_login_check`` route is automatically defined by Silex and its + name is derived from the ``check_path`` value (all ``/`` are replaced with + ``_`` and the leading ``/`` is stripped). + +Defining more than one Firewall +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You are not limited to define one firewall per project. + +Configuring several firewalls is useful when you want to secure different +parts of your website with different authentication strategies or for +different users (like using an HTTP basic authentication for the website API +and a form to secure your website administration area). + +It's also useful when you want to secure all URLs except the login form:: + + $app['security.firewalls'] = array( + 'login' => array( + 'pattern' => '^/login$', + ), + 'secured' => array( + 'pattern' => '^.*$', + 'form' => array('login_path' => '/login', 'check_path' => '/login_check'), + 'users' => array( + 'admin' => array('ROLE_ADMIN', '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a'), + ), + ), + ); + +The order of the firewall configurations is significant as the first one to +match wins. The above configuration first ensures that the ``/login`` URL is +not secured (no authentication settings), and then it secures all other URLs. + +.. tip:: + + You can toggle all registered authentication mechanisms for a particular + area on and off with the ``security`` flag:: + + $app['security.firewalls'] = array( + 'api' => array( + 'pattern' => '^/api', + 'security' => $app['debug'] ? false : true, + 'wsse' => true, + + // ... + ), + ); + +Adding a Logout +~~~~~~~~~~~~~~~ + +When using a form for authentication, you can let users log out if you add the +``logout`` setting, where ``logout_path`` must match the main firewall +pattern:: + + $app['security.firewalls'] = array( + 'secured' => array( + 'pattern' => '^/admin/', + 'form' => array('login_path' => '/login', 'check_path' => '/admin/login_check'), + 'logout' => array('logout_path' => '/admin/logout', 'invalidate_session' => true), + + // ... + ), + ); + +A route is automatically generated, based on the configured path (all ``/`` +are replaced with ``_`` and the leading ``/`` is stripped): + +.. code-block:: jinja + + Logout + +Allowing Anonymous Users +~~~~~~~~~~~~~~~~~~~~~~~~ + +When securing only some parts of your website, the user information are not +available in non-secured areas. To make the user accessible in such areas, +enabled the ``anonymous`` authentication mechanism:: + + $app['security.firewalls'] = array( + 'unsecured' => array( + 'anonymous' => true, + + // ... + ), + ); + +When enabling the anonymous setting, a user will always be accessible from the +security context; if the user is not authenticated, it returns the ``anon.`` +string. + +Checking User Roles +~~~~~~~~~~~~~~~~~~~ + +To check if a user is granted some role, use the ``isGranted()`` method on the +security context:: + + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + // ... + } + +You can check roles in Twig templates too: + +.. code-block:: jinja + + {% if is_granted('ROLE_ADMIN') %} + Switch to Fabien + {% endif %} + +You can check if a user is "fully authenticated" (not an anonymous user for +instance) with the special ``IS_AUTHENTICATED_FULLY`` role: + +.. code-block:: jinja + + {% if is_granted('IS_AUTHENTICATED_FULLY') %} + Logout + {% else %} + Login + {% endif %} + +Of course you will need to define a ``login`` route for this to work. + +.. tip:: + + Don't use the ``getRoles()`` method to check user roles. + +.. caution:: + + ``isGranted()`` throws an exception when no authentication information is + available (which is the case on non-secured area). + +Impersonating a User +~~~~~~~~~~~~~~~~~~~~ + +If you want to be able to switch to another user (without knowing the user +credentials), enable the ``switch_user`` authentication strategy:: + + $app['security.firewalls'] = array( + 'unsecured' => array( + 'switch_user' => array('parameter' => '_switch_user', 'role' => 'ROLE_ALLOWED_TO_SWITCH'), + + // ... + ), + ); + +Switching to another user is now a matter of adding the ``_switch_user`` query +parameter to any URL when logged in as a user who has the +``ROLE_ALLOWED_TO_SWITCH`` role: + +.. code-block:: jinja + + {% if is_granted('ROLE_ALLOWED_TO_SWITCH') %} + Switch to user Fabien + {% endif %} + +You can check that you are impersonating a user by checking the special +``ROLE_PREVIOUS_ADMIN``. This is useful for instance to allow the user to +switch back to their primary account: + +.. code-block:: jinja + + {% if is_granted('ROLE_PREVIOUS_ADMIN') %} + You are an admin but you've switched to another user, + exit the switch. + {% endif %} + +Defining a Role Hierarchy +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Defining a role hierarchy allows to automatically grant users some additional +roles:: + + $app['security.role_hierarchy'] = array( + 'ROLE_ADMIN' => array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'), + ); + +With this configuration, all users with the ``ROLE_ADMIN`` role also +automatically have the ``ROLE_USER`` and ``ROLE_ALLOWED_TO_SWITCH`` roles. + +Defining Access Rules +~~~~~~~~~~~~~~~~~~~~~ + +Roles are a great way to adapt the behavior of your website depending on +groups of users, but they can also be used to further secure some areas by +defining access rules:: + + $app['security.access_rules'] = array( + array('^/admin', 'ROLE_ADMIN', 'https'), + array('^.*$', 'ROLE_USER'), + ); + +With the above configuration, users must have the ``ROLE_ADMIN`` to access the +``/admin`` section of the website, and ``ROLE_USER`` for everything else. +Furthermore, the admin section can only be accessible via HTTPS (if that's not +the case, the user will be automatically redirected). + +.. note:: + + The first argument can also be a `RequestMatcher + `_ + instance. + +Defining a custom User Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using an array of users is simple and useful when securing an admin section of +a personal website, but you can override this default mechanism with you own. + +The ``users`` setting can be defined as a service that returns an instance of +`UserProviderInterface +`_:: + + 'users' => function () use ($app) { + return new UserProvider($app['db']); + }, + +Here is a simple example of a user provider, where Doctrine DBAL is used to +store the users:: + + use Symfony\Component\Security\Core\User\UserProviderInterface; + use Symfony\Component\Security\Core\User\UserInterface; + use Symfony\Component\Security\Core\User\User; + use Symfony\Component\Security\Core\Exception\UnsupportedUserException; + use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; + use Doctrine\DBAL\Connection; + + class UserProvider implements UserProviderInterface + { + private $conn; + + public function __construct(Connection $conn) + { + $this->conn = $conn; + } + + public function loadUserByUsername($username) + { + $stmt = $this->conn->executeQuery('SELECT * FROM users WHERE username = ?', array(strtolower($username))); + + if (!$user = $stmt->fetch()) { + throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); + } + + return new User($user['username'], $user['password'], explode(',', $user['roles']), true, true, true, true); + } + + public function refreshUser(UserInterface $user) + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); + } + + return $this->loadUserByUsername($user->getUsername()); + } + + public function supportsClass($class) + { + return $class === 'Symfony\Component\Security\Core\User\User'; + } + } + +In this example, instances of the default ``User`` class are created for the +users, but you can define your own class; the only requirement is that the +class must implement `UserInterface +`_ + +And here is the code that you can use to create the database schema and some +sample users:: + + use Doctrine\DBAL\Schema\Table; + + $schema = $app['db']->getSchemaManager(); + if (!$schema->tablesExist('users')) { + $users = new Table('users'); + $users->addColumn('id', 'integer', array('unsigned' => true, 'autoincrement' => true)); + $users->setPrimaryKey(array('id')); + $users->addColumn('username', 'string', array('length' => 32)); + $users->addUniqueIndex(array('username')); + $users->addColumn('password', 'string', array('length' => 255)); + $users->addColumn('roles', 'string', array('length' => 255)); + + $schema->createTable($users); + + $app['db']->insert('users', array( + 'username' => 'fabien', + 'password' => '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a', + 'roles' => 'ROLE_USER' + )); + + $app['db']->insert('users', array( + 'username' => 'admin', + 'password' => '$2y$10$3i9/lVd8UOFIJ6PAMFt8gu3/r5g0qeCJvoSlLCsvMTythye19F77a', + 'roles' => 'ROLE_ADMIN' + )); + } + +.. tip:: + + If you are using the Doctrine ORM, the Symfony bridge for Doctrine + provides a user provider class that is able to load users from your + entities. + +Defining a custom Encoder +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, Silex uses the ``BCrypt`` algorithm to encode passwords. +Additionally, the password is encoded multiple times. +You can change these defaults by overriding ``security.default_encoder`` +service to return one of the predefined encoders: + +* **security.encoder.digest**: Digest password encoder. + +* **security.encoder.bcrypt**: BCrypt password encoder. + +* **security.encoder.pbkdf2**: Pbkdf2 password encoder. + +.. code-block:: php + + $app['security.default_encoder'] = function ($app) { + return $app['security.encoder.pbkdf2']; + }; + +Or you can define you own, fully customizable encoder:: + + use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder; + + $app['security.default_encoder'] = function ($app) { + // Plain text (e.g. for debugging) + return new PlaintextPasswordEncoder(); + }; + +.. tip:: + + You can change the default BCrypt encoding cost by overriding ``security.encoder.bcrypt.cost`` + +Defining a custom Authentication Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Symfony Security component provides a lot of ready-to-use authentication +providers (form, HTTP, X509, remember me, ...), but you can add new ones +easily. To register a new authentication provider, create a service named +``security.authentication_listener.factory.XXX`` where ``XXX`` is the name you want to +use in your configuration:: + + $app['security.authentication_listener.factory.wsse'] = $app->protect(function ($name, $options) use ($app) { + // define the authentication provider object + $app['security.authentication_provider.'.$name.'.wsse'] = function () use ($app) { + return new WsseProvider($app['security.user_provider.default'], __DIR__.'/security_cache'); + }; + + // define the authentication listener object + $app['security.authentication_listener.'.$name.'.wsse'] = function () use ($app) { + return new WsseListener($app['security.token_storage'], $app['security.authentication_manager']); + }; + + return array( + // the authentication provider id + 'security.authentication_provider.'.$name.'.wsse', + // the authentication listener id + 'security.authentication_listener.'.$name.'.wsse', + // the entry point id + null, + // the position of the listener in the stack + 'pre_auth' + ); + }); + +You can now use it in your configuration like any other built-in +authentication provider:: + + $app->register(new Silex\Provider\SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'wsse' => true, + + // ... + ), + ), + )); + +Instead of ``true``, you can also define an array of options that customize +the behavior of your authentication factory; it will be passed as the second +argument of your authentication factory (see above). + +This example uses the authentication provider classes as described in the +Symfony `cookbook`_. + + +.. note:: + + The Guard component simplifies the creation of custom authentication + providers. :doc:`How to Create a Custom Authentication System with Guard + ` + +Stateless Authentication +~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, a session cookie is created to persist the security context of +the user. However, if you use certificates, HTTP authentication, WSSE and so +on, the credentials are sent for each request. In that case, you can turn off +persistence by activating the ``stateless`` authentication flag:: + + $app['security.firewalls'] = array( + 'default' => array( + 'stateless' => true, + 'wsse' => true, + + // ... + ), + ); + +Traits +------ + +``Silex\Application\SecurityTrait`` adds the following shortcuts: + +* **encodePassword**: Encode a given password. + +.. code-block:: php + + $user = $app->user(); + + $encoded = $app->encodePassword($user, 'foo'); + +``Silex\Route\SecurityTrait`` adds the following methods to the controllers: + +* **secure**: Secures a controller for the given roles. + +.. code-block:: php + + $app->get('/', function () { + // do something but only for admins + })->secure('ROLE_ADMIN'); + +.. caution:: + + The ``Silex\Route\SecurityTrait`` must be used with a user defined + ``Route`` class, not the application. + + .. code-block:: php + + use Silex\Route; + + class MyRoute extends Route + { + use Route\SecurityTrait; + } + + .. code-block:: php + + $app['route_class'] = 'MyRoute'; + + +.. _cookbook: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html diff --git a/vendor/silex/silex/doc/providers/serializer.rst b/vendor/silex/silex/doc/providers/serializer.rst new file mode 100644 index 00000000..162dbab6 --- /dev/null +++ b/vendor/silex/silex/doc/providers/serializer.rst @@ -0,0 +1,73 @@ +Serializer +========== + +The *SerializerServiceProvider* provides a service for serializing objects. + +Parameters +---------- + +None. + +Services +-------- + +* **serializer**: An instance of `Symfony\\Component\\Serializer\\Serializer + `_. + +* **serializer.encoders**: `Symfony\\Component\\Serializer\\Encoder\\JsonEncoder + `_ + and `Symfony\\Component\\Serializer\\Encoder\\XmlEncoder + `_. + +* **serializer.normalizers**: `Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer + `_ + and `Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer + `_. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SerializerServiceProvider()); + +.. note:: + + Add the Symfony's `Serializer Component + `_ as a + dependency: + + .. code-block:: bash + + composer require symfony/serializer + +Usage +----- + +The ``SerializerServiceProvider`` provider provides a ``serializer`` service:: + + use Silex\Application; + use Silex\Provider\SerializerServiceProvider; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app = new Application(); + + $app->register(new SerializerServiceProvider()); + + // only accept content types supported by the serializer via the assert method. + $app->get("/pages/{id}.{_format}", function (Request $request, $id) use ($app) { + // assume a page_repository service exists that returns Page objects. The + // object returned has getters and setters exposing the state. + $page = $app['page_repository']->find($id); + $format = $request->getRequestFormat(); + + if (!$page instanceof Page) { + $app->abort("No page found for id: $id"); + } + + return new Response($app['serializer']->serialize($page, $format), 200, array( + "Content-Type" => $request->getMimeType($format) + )); + })->assert("_format", "xml|json") + ->assert("id", "\d+"); diff --git a/vendor/silex/silex/doc/providers/service_controller.rst b/vendor/silex/silex/doc/providers/service_controller.rst new file mode 100644 index 00000000..15bca28d --- /dev/null +++ b/vendor/silex/silex/doc/providers/service_controller.rst @@ -0,0 +1,142 @@ +Service Controllers +=================== + +As your Silex application grows, you may wish to begin organizing your +controllers in a more formal fashion. Silex can use controller classes out of +the box, but with a bit of work, your controllers can be created as services, +giving you the full power of dependency injection and lazy loading. + +.. ::todo Link above to controller classes cookbook + +Why would I want to do this? +---------------------------- + +- Dependency Injection over Service Location + + Using this method, you can inject the actual dependencies required by your + controller and gain total inversion of control, while still maintaining the + lazy loading of your controllers and its dependencies. Because your + dependencies are clearly defined, they are easily mocked, allowing you to test + your controllers in isolation. + +- Framework Independence + + Using this method, your controllers start to become more independent of the + framework you are using. Carefully crafted, your controllers will become + reusable with multiple frameworks. By keeping careful control of your + dependencies, your controllers could easily become compatible with Silex, + Symfony (full stack) and Drupal, to name just a few. + +Parameters +---------- + +There are currently no parameters for the ``ServiceControllerServiceProvider``. + +Services +-------- + +There are no extra services provided, the ``ServiceControllerServiceProvider`` +simply extends the existing **resolver** service. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\ServiceControllerServiceProvider()); + +Usage +----- + +In this slightly contrived example of a blog API, we're going to change the +``/posts.json`` route to use a controller, that is defined as a service. + +.. code-block:: php + + use Silex\Application; + use Demo\Repository\PostRepository; + + $app = new Application(); + + $app['posts.repository'] = function() { + return new PostRepository; + }; + + $app->get('/posts.json', function() use ($app) { + return $app->json($app['posts.repository']->findAll()); + }); + +Rewriting your controller as a service is pretty simple, create a Plain Ol' PHP +Object with your ``PostRepository`` as a dependency, along with an +``indexJsonAction`` method to handle the request. Although not shown in the +example below, you can use type hinting and parameter naming to get the +parameters you need, just like with standard Silex routes. + +If you are a TDD/BDD fan (and you should be), you may notice that this +controller has well defined responsibilities and dependencies, and is easily +tested/specced. You may also notice that the only external dependency is on +``Symfony\Component\HttpFoundation\JsonResponse``, meaning this controller could +easily be used in a Symfony (full stack) application, or potentially with other +applications or frameworks that know how to handle a `Symfony/HttpFoundation +`_ +``Response`` object. + +.. code-block:: php + + namespace Demo\Controller; + + use Demo\Repository\PostRepository; + use Symfony\Component\HttpFoundation\JsonResponse; + + class PostController + { + protected $repo; + + public function __construct(PostRepository $repo) + { + $this->repo = $repo; + } + + public function indexJsonAction() + { + return new JsonResponse($this->repo->findAll()); + } + } + +And lastly, define your controller as a service in the application, along with +your route. The syntax in the route definition is the name of the service, +followed by a single colon (:), followed by the method name. + +.. code-block:: php + + $app['posts.controller'] = function() use ($app) { + return new PostController($app['posts.repository']); + }; + + $app->get('/posts.json', "posts.controller:indexJsonAction"); + +In addition to using classes for service controllers, you can define any +callable as a service in the application to be used for a route. + +.. code-block:: php + + namespace Demo\Controller; + + use Demo\Repository\PostRepository; + use Symfony\Component\HttpFoundation\JsonResponse; + + function postIndexJson(PostRepository $repo) { + return function() use ($repo) { + return new JsonResponse($repo->findAll()); + }; + } + +And when defining your route, the code would look like the following: + +.. code-block:: php + + $app['posts.controller'] = function($app) { + return Demo\Controller\postIndexJson($app['posts.repository']); + }; + + $app->get('/posts.json', 'posts.controller'); diff --git a/vendor/silex/silex/doc/providers/session.rst b/vendor/silex/silex/doc/providers/session.rst new file mode 100644 index 00000000..011b69fe --- /dev/null +++ b/vendor/silex/silex/doc/providers/session.rst @@ -0,0 +1,120 @@ +Session +======= + +The *SessionServiceProvider* provides a service for storing data persistently +between requests. + +Parameters +---------- + +* **session.storage.save_path** (optional): The path for the + ``NativeFileSessionHandler``, defaults to the value of + ``sys_get_temp_dir()``. + +* **session.storage.options**: An array of options that is passed to the + constructor of the ``session.storage`` service. + + In case of the default `NativeSessionStorage + `_, + the most useful options are: + + * **name**: The cookie name (_SESS by default) + * **id**: The session id (null by default) + * **cookie_lifetime**: Cookie lifetime + * **cookie_path**: Cookie path + * **cookie_domain**: Cookie domain + * **cookie_secure**: Cookie secure (HTTPS) + * **cookie_httponly**: Whether the cookie is http only + + However, all of these are optional. Default Sessions life time is 1800 + seconds (30 minutes). To override this, set the ``lifetime`` option. + + For a full list of available options, read the `PHP + `_ official documentation. + +* **session.test**: Whether to simulate sessions or not (useful when writing + functional tests). + +Services +-------- + +* **session**: An instance of Symfony's `Session + `_. + +* **session.storage**: A service that is used for persistence of the session + data. + +* **session.storage.handler**: A service that is used by the + ``session.storage`` for data access. Defaults to a `NativeFileSessionHandler + `_ + storage handler. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SessionServiceProvider()); + +Using Handlers +-------------- + +The default session handler is ``NativeFileSessionHandler``. However, there are +multiple handlers available for use by setting ``session.storage.handler`` to +an instance of one of the following handler objects: + +* `LegacyPdoSessionHandler `_ +* `MemcacheSessionHandler `_ +* `MemcachedSessionHandler `_ +* `MongoDbSessionHandler `_ +* `NativeFileSessionHandler `_ +* `NativeSessionHandler `_ +* `NullSessionHandler `_ +* `PdoSessionHandler `_ +* `WriteCheckSessionHandler `_ + +Usage +----- + +The Session provider provides a ``session`` service. Here is an example that +authenticates a user and creates a session for them:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app->get('/login', function (Request $request) use ($app) { + $username = $request->server->get('PHP_AUTH_USER', false); + $password = $request->server->get('PHP_AUTH_PW'); + + if ('igor' === $username && 'password' === $password) { + $app['session']->set('user', array('username' => $username)); + return $app->redirect('/account'); + } + + $response = new Response(); + $response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', 'site_login')); + $response->setStatusCode(401, 'Please sign in.'); + return $response; + }); + + $app->get('/account', function () use ($app) { + if (null === $user = $app['session']->get('user')) { + return $app->redirect('/login'); + } + + return "Welcome {$user['username']}!"; + }); + + +Custom Session Configurations +----------------------------- + +If your system is using a custom session configuration (such as a redis handler +from a PHP extension) then you need to disable the NativeFileSessionHandler by +setting ``session.storage.handler`` to null. You will have to configure the +``session.save_path`` ini setting yourself in that case. + +.. code-block:: php + + $app['session.storage.handler'] = null; + diff --git a/vendor/silex/silex/doc/providers/swiftmailer.rst b/vendor/silex/silex/doc/providers/swiftmailer.rst new file mode 100644 index 00000000..9297d665 --- /dev/null +++ b/vendor/silex/silex/doc/providers/swiftmailer.rst @@ -0,0 +1,156 @@ +Swiftmailer +=========== + +The *SwiftmailerServiceProvider* provides a service for sending email through +the `Swift Mailer `_ library. + +You can use the ``mailer`` service to send messages easily. By default, it +will attempt to send emails through SMTP. + +Parameters +---------- + +* **swiftmailer.use_spool**: A boolean to specify whether or not to use the + memory spool, defaults to true. + +* **swiftmailer.options**: An array of options for the default SMTP-based + configuration. + + The following options can be set: + + * **host**: SMTP hostname, defaults to 'localhost'. + * **port**: SMTP port, defaults to 25. + * **username**: SMTP username, defaults to an empty string. + * **password**: SMTP password, defaults to an empty string. + * **encryption**: SMTP encryption, defaults to null. Valid values are 'tls', 'ssl', or null (indicating no encryption). + * **auth_mode**: SMTP authentication mode, defaults to null. Valid values are 'plain', 'login', 'cram-md5', or null. + + Example usage:: + + $app['swiftmailer.options'] = array( + 'host' => 'host', + 'port' => '25', + 'username' => 'username', + 'password' => 'password', + 'encryption' => null, + 'auth_mode' => null + ); + +* **swiftmailer.sender_address**: If set, all messages will be delivered with + this address as the "return path" address. + +* **swiftmailer.delivery_addresses**: If not empty, all email messages will be + sent to those addresses instead of being sent to their actual recipients. This + is often useful when developing. + +* **swiftmailer.delivery_whitelist**: Used in combination with + ``delivery_addresses``. If set, emails matching any of these patterns will be + delivered like normal, as well as being sent to ``delivery_addresses``. + +* **swiftmailer.plugins**: Array of SwiftMailer plugins. + + Example usage:: + + $app['swiftmailer.plugins'] = function ($app) { + return array( + new \Swift_Plugins_PopBeforeSmtpPlugin('pop3.example.com'), + ); + }; + +Services +-------- + +* **mailer**: The mailer instance. + + Example usage:: + + $message = \Swift_Message::newInstance(); + + // ... + + $app['mailer']->send($message); + +* **swiftmailer.transport**: The transport used for e-mail + delivery. Defaults to a ``Swift_Transport_EsmtpTransport``. + +* **swiftmailer.transport.buffer**: StreamBuffer used by + the transport. + +* **swiftmailer.transport.authhandler**: Authentication + handler used by the transport. Will try the following + by default: CRAM-MD5, login, plaintext. + +* **swiftmailer.transport.eventdispatcher**: Internal event + dispatcher used by Swiftmailer. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\SwiftmailerServiceProvider()); + +.. note:: + + Add SwiftMailer as a dependency: + + .. code-block:: bash + + composer require swiftmailer/swiftmailer + +Usage +----- + +The Swiftmailer provider provides a ``mailer`` service:: + + use Symfony\Component\HttpFoundation\Request; + + $app->post('/feedback', function (Request $request) use ($app) { + $message = \Swift_Message::newInstance() + ->setSubject('[YourSite] Feedback') + ->setFrom(array('noreply@yoursite.com')) + ->setTo(array('feedback@yoursite.com')) + ->setBody($request->get('message')); + + $app['mailer']->send($message); + + return new Response('Thank you for your feedback!', 201); + }); + +Usage in commands +~~~~~~~~~~~~~~~~~ + +By default, the Swiftmailer provider sends the emails using the ``KernelEvents::TERMINATE`` +event, which is fired after the response has been sent. However, as this event +isn't fired for console commands, your emails won't be sent. + +For that reason, if you send emails using a command console, it is recommended +that you disable the use of the memory spool (before accessing ``$app['mailer']``):: + + $app['swiftmailer.use_spool'] = false; + +Alternatively, you can just make sure to flush the message spool by hand before +ending the command execution. To do so, use the following code:: + + $app['swiftmailer.spooltransport'] + ->getSpool() + ->flushQueue($app['swiftmailer.transport']) + ; + +Traits +------ + +``Silex\Application\SwiftmailerTrait`` adds the following shortcuts: + +* **mail**: Sends an email. + +.. code-block:: php + + $app->mail(\Swift_Message::newInstance() + ->setSubject('[YourSite] Feedback') + ->setFrom(array('noreply@yoursite.com')) + ->setTo(array('feedback@yoursite.com')) + ->setBody($request->get('message'))); + +For more information, check out the `Swift Mailer documentation +`_. diff --git a/vendor/silex/silex/doc/providers/translation.rst b/vendor/silex/silex/doc/providers/translation.rst new file mode 100644 index 00000000..145fc18c --- /dev/null +++ b/vendor/silex/silex/doc/providers/translation.rst @@ -0,0 +1,193 @@ +Translation +=========== + +The *TranslationServiceProvider* provides a service for translating your +application into different languages. + +Parameters +---------- + +* **translator.domains** (optional): A mapping of domains/locales/messages. + This parameter contains the translation data for all languages and domains. + +* **locale** (optional): The locale for the translator. You will most likely + want to set this based on some request parameter. Defaults to ``en``. + +* **locale_fallbacks** (optional): Fallback locales for the translator. It will + be used when the current locale has no messages set. Defaults to ``en``. + +Services +-------- + +* **translator**: An instance of `Translator + `_, + that is used for translation. + +* **translator.loader**: An instance of an implementation of the translation + `LoaderInterface + `_, + defaults to an `ArrayLoader + `_. + +* **translator.message_selector**: An instance of `MessageSelector + `_. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\LocaleServiceProvider()); + $app->register(new Silex\Provider\TranslationServiceProvider(), array( + 'locale_fallbacks' => array('en'), + )); + +.. note:: + + Add the Symfony Translation Component as a dependency: + + .. code-block:: bash + + composer require symfony/translation + +Usage +----- + +The Translation provider provides a ``translator`` service and makes use of +the ``translator.domains`` parameter:: + + $app['translator.domains'] = array( + 'messages' => array( + 'en' => array( + 'hello' => 'Hello %name%', + 'goodbye' => 'Goodbye %name%', + ), + 'de' => array( + 'hello' => 'Hallo %name%', + 'goodbye' => 'Tschüss %name%', + ), + 'fr' => array( + 'hello' => 'Bonjour %name%', + 'goodbye' => 'Au revoir %name%', + ), + ), + 'validators' => array( + 'fr' => array( + 'This value should be a valid number.' => 'Cette valeur doit être un nombre.', + ), + ), + ); + + $app->get('/{_locale}/{message}/{name}', function ($message, $name) use ($app) { + return $app['translator']->trans($message, array('%name%' => $name)); + }); + +The above example will result in following routes: + +* ``/en/hello/igor`` will return ``Hello igor``. + +* ``/de/hello/igor`` will return ``Hallo igor``. + +* ``/fr/hello/igor`` will return ``Bonjour igor``. + +* ``/it/hello/igor`` will return ``Hello igor`` (because of the fallback). + +Using Resources +--------------- + +When translations are stored in a file, you can load them as follows:: + + $app = new Application(); + + $app->register(new TranslationServiceProvider()); + $app->extend('translator.resources', function ($resources, $app) { + $resources = array_merge($resources, array( + array('array', array('This value should be a valid number.' => 'Cette valeur doit être un nombre.'), 'fr', 'validators'), + )); + + return $resources; + }); + +Traits +------ + +``Silex\Application\TranslationTrait`` adds the following shortcuts: + +* **trans**: Translates the given message. + +* **transChoice**: Translates the given choice message by choosing a + translation according to a number. + +.. code-block:: php + + $app->trans('Hello World'); + + $app->transChoice('Hello World'); + +Recipes +------- + +YAML-based language files +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Having your translations in PHP files can be inconvenient. This recipe will +show you how to load translations from external YAML files. + +First, add the Symfony ``Config`` and ``Yaml`` components as dependencies: + +.. code-block:: bash + + composer require symfony/config symfony/yaml + +Next, you have to create the language mappings in YAML files. A naming you can +use is ``locales/en.yml``. Just do the mapping in this file as follows: + +.. code-block:: yaml + + hello: Hello %name% + goodbye: Goodbye %name% + +Then, register the ``YamlFileLoader`` on the ``translator`` and add all your +translation files:: + + use Symfony\Component\Translation\Loader\YamlFileLoader; + + $app->extend('translator', function($translator, $app) { + $translator->addLoader('yaml', new YamlFileLoader()); + + $translator->addResource('yaml', __DIR__.'/locales/en.yml', 'en'); + $translator->addResource('yaml', __DIR__.'/locales/de.yml', 'de'); + $translator->addResource('yaml', __DIR__.'/locales/fr.yml', 'fr'); + + return $translator; + }); + +XLIFF-based language files +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Just as you would do with YAML translation files, you first need to add the +Symfony ``Config`` component as a dependency (see above for details). + +Then, similarly, create XLIFF files in your locales directory and add them to +the translator:: + + $translator->addResource('xliff', __DIR__.'/locales/en.xlf', 'en'); + $translator->addResource('xliff', __DIR__.'/locales/de.xlf', 'de'); + $translator->addResource('xliff', __DIR__.'/locales/fr.xlf', 'fr'); + +.. note:: + + The XLIFF loader is already pre-configured by the extension. + +Accessing translations in Twig templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once loaded, the translation service provider is available from within Twig +templates when using the Twig bridge provided by Symfony (see +:doc:`TwigServiceProvider
`): + +.. code-block:: jinja + + {{ 'translation_key'|trans }} + {{ 'translation_key'|transchoice }} + {% trans %}translation_key{% endtrans %} diff --git a/vendor/silex/silex/doc/providers/twig.rst b/vendor/silex/silex/doc/providers/twig.rst new file mode 100644 index 00000000..b713e1a3 --- /dev/null +++ b/vendor/silex/silex/doc/providers/twig.rst @@ -0,0 +1,237 @@ +Twig +==== + +The *TwigServiceProvider* provides integration with the `Twig +`_ template engine. + +Parameters +---------- + +* **twig.path** (optional): Path to the directory containing twig template + files (it can also be an array of paths). + +* **twig.templates** (optional): An associative array of template names to + template contents. Use this if you want to define your templates inline. + +* **twig.options** (optional): An associative array of twig + options. Check out the `twig documentation `_ + for more information. + +* **twig.form.templates** (optional): An array of templates used to render + forms (only available when the ``FormServiceProvider`` is enabled). The + default theme is ``form_div_layout.html.twig``, but you can use the other + built-in themes: ``form_table_layout.html.twig``, + ``bootstrap_3_layout.html.twig``, and + ``bootstrap_3_horizontal_layout.html.twig``. + +* **twig.date.format** (optional): Default format used by the ``date`` + filter. The format string must conform to the format accepted by + `date() `_. + +* **twig.date.interval_format** (optional): Default format used by the + ``date`` filter when the filtered data is of type `DateInterval `_. + The format string must conform to the format accepted by + `DateInterval::format() `_. + +* **twig.date.timezone** (optional): Default timezone used when formatting + dates. If set to ``null`` the timezone returned by `date_default_timezone_get() `_ + is used. + +* **twig.number_format.decimals** (optional): Default number of decimals + displayed by the ``number_format`` filter. + +* **twig.number_format.decimal_point** (optional): Default separator for + the decimal point used by the ``number_format`` filter. + +* **twig.number_format.thousands_separator** (optional): Default thousands + separator used by the ``number_format`` filter. + +Services +-------- + +* **twig**: The ``Twig_Environment`` instance. The main way of + interacting with Twig. + +* **twig.loader**: The loader for Twig templates which uses the ``twig.path`` + and the ``twig.templates`` options. You can also replace the loader + completely. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\TwigServiceProvider(), array( + 'twig.path' => __DIR__.'/views', + )); + +.. note:: + + Add Twig as a dependency: + + .. code-block:: bash + + composer require twig/twig + +Usage +----- + +The Twig provider provides a ``twig`` service that can render templates:: + + $app->get('/hello/{name}', function ($name) use ($app) { + return $app['twig']->render('hello.twig', array( + 'name' => $name, + )); + }); + +Symfony Components Integration +------------------------------ + +Symfony provides a Twig bridge that provides additional integration between +some Symfony components and Twig. Add it as a dependency: + +.. code-block:: bash + + composer require symfony/twig-bridge + +When present, the ``TwigServiceProvider`` will provide you with the following +additional capabilities. + +* Access to the ``path()`` and ``url()`` functions. You can find more + information in the `Symfony Routing documentation + `_: + + .. code-block:: jinja + + {{ path('homepage') }} + {{ url('homepage') }} {# generates the absolute url http://example.org/ #} + {{ path('hello', {name: 'Fabien'}) }} + {{ url('hello', {name: 'Fabien'}) }} {# generates the absolute url http://example.org/hello/Fabien #} + +* Access to the ``absolute_url()`` and ``relative_path()`` Twig functions. + +Translations Support +~~~~~~~~~~~~~~~~~~~~ + +If you are using the ``TranslationServiceProvider``, you will get the +``trans()`` and ``transchoice()`` functions for translation in Twig templates. +You can find more information in the `Symfony Translation documentation +`_. + +Form Support +~~~~~~~~~~~~ + +If you are using the ``FormServiceProvider``, you will get a set of helpers for +working with forms in templates. You can find more information in the `Symfony +Forms reference +`_. + +Security Support +~~~~~~~~~~~~~~~~ + +If you are using the ``SecurityServiceProvider``, you will have access to the +``is_granted()`` function in templates. You can find more information in the +`Symfony Security documentation +`_. + +Web Link Support +~~~~~~~~~~~~~~~~ + +If you are using the ``symfony/web-link`` component, you will have access to the +``preload()``, ``prefetch()``, ``prerender()``, ``dns_prefetch()``, +``preconnect()`` and ``link()`` functions in templates. You can find more +information in the `Symfony WebLink documentation +`_. + +Global Variable +~~~~~~~~~~~~~~~ + +When the Twig bridge is available, the ``global`` variable refers to an +instance of `AppVariable `_. +It gives access to the following methods: + +.. code-block:: jinja + + {# The current Request #} + {{ global.request }} + + {# The current User (when security is enabled) #} + {{ global.user }} + + {# The current Session #} + {{ global.session }} + + {# The debug flag #} + {{ global.debug }} + +Rendering a Controller +~~~~~~~~~~~~~~~~~~~~~~ + +A ``render`` function is also registered to help you render another controller +from a template (available when the :doc:`HttpFragment Service Provider +
` is registered): + +.. code-block:: jinja + + {{ render(url('sidebar')) }} + + {# or you can reference a controller directly without defining a route for it #} + {{ render(controller(controller)) }} + +.. note:: + + You must prepend the ``app.request.baseUrl`` to render calls to ensure + that the render works when deployed into a sub-directory of the docroot. + +.. note:: + + Read the Twig `reference`_ for Symfony document to learn more about the + various Twig functions. + +Traits +------ + +``Silex\Application\TwigTrait`` adds the following shortcuts: + +* **render**: Renders a view with the given parameters and returns a Response + object. + +.. code-block:: php + + return $app->render('index.html', ['name' => 'Fabien']); + + $response = new Response(); + $response->setTtl(10); + + return $app->render('index.html', ['name' => 'Fabien'], $response); + +.. code-block:: php + + // stream a view + use Symfony\Component\HttpFoundation\StreamedResponse; + + return $app->render('index.html', ['name' => 'Fabien'], new StreamedResponse()); + +* **renderView**: Renders a view with the given parameters and returns a string. + +.. code-block:: php + + $content = $app->renderView('index.html', ['name' => 'Fabien']); + +Customization +------------- + +You can configure the Twig environment before using it by extending the +``twig`` service:: + + $app->extend('twig', function($twig, $app) { + $twig->addGlobal('pi', 3.14); + $twig->addFilter('levenshtein', new \Twig_Filter_Function('levenshtein')); + + return $twig; + }); + +For more information, check out the `official Twig documentation +`_. + +.. _reference: https://symfony.com/doc/current/reference/twig_reference.html#controller diff --git a/vendor/silex/silex/doc/providers/validator.rst b/vendor/silex/silex/doc/providers/validator.rst new file mode 100644 index 00000000..bd4e9985 --- /dev/null +++ b/vendor/silex/silex/doc/providers/validator.rst @@ -0,0 +1,217 @@ +Validator +========= + +The *ValidatorServiceProvider* provides a service for validating data. It is +most useful when used with the *FormServiceProvider*, but can also be used +standalone. + +Parameters +---------- + +* **validator.validator_service_ids**: An array of service names representing + validators. + +Services +-------- + +* **validator**: An instance of `Validator + `_. + +* **validator.mapping.class_metadata_factory**: Factory for metadata loaders, + which can read validation constraint information from classes. Defaults to + StaticMethodLoader--ClassMetadataFactory. + + This means you can define a static ``loadValidatorMetadata`` method on your + data class, which takes a ClassMetadata argument. Then you can set + constraints on this ClassMetadata instance. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\ValidatorServiceProvider()); + +.. note:: + + Add the Symfony Validator Component as a dependency: + + .. code-block:: bash + + composer require symfony/validator + +Usage +----- + +The Validator provider provides a ``validator`` service. + +Validating Values +~~~~~~~~~~~~~~~~~ + +You can validate values directly using the ``validate`` validator +method:: + + use Symfony\Component\Validator\Constraints as Assert; + + $app->get('/validate/{email}', function ($email) use ($app) { + $errors = $app['validator']->validate($email, new Assert\Email()); + + if (count($errors) > 0) { + return (string) $errors; + } else { + return 'The email is valid'; + } + }); + +Validating Associative Arrays +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Validating associative arrays is like validating simple values, with a +collection of constraints:: + + use Symfony\Component\Validator\Constraints as Assert; + + $book = array( + 'title' => 'My Book', + 'author' => array( + 'first_name' => 'Fabien', + 'last_name' => 'Potencier', + ), + ); + + $constraint = new Assert\Collection(array( + 'title' => new Assert\Length(array('min' => 10)), + 'author' => new Assert\Collection(array( + 'first_name' => array(new Assert\NotBlank(), new Assert\Length(array('min' => 10))), + 'last_name' => new Assert\Length(array('min' => 10)), + )), + )); + $errors = $app['validator']->validate($book, $constraint); + + if (count($errors) > 0) { + foreach ($errors as $error) { + echo $error->getPropertyPath().' '.$error->getMessage()."\n"; + } + } else { + echo 'The book is valid'; + } + +Validating Objects +~~~~~~~~~~~~~~~~~~ + +If you want to add validations to a class, you can define the constraint for +the class properties and getters, and then call the ``validate`` method:: + + use Symfony\Component\Validator\Constraints as Assert; + + class Book + { + public $title; + public $author; + } + + class Author + { + public $first_name; + public $last_name; + } + + $author = new Author(); + $author->first_name = 'Fabien'; + $author->last_name = 'Potencier'; + + $book = new Book(); + $book->title = 'My Book'; + $book->author = $author; + + $metadata = $app['validator.mapping.class_metadata_factory']->getMetadataFor('Author'); + $metadata->addPropertyConstraint('first_name', new Assert\NotBlank()); + $metadata->addPropertyConstraint('first_name', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('last_name', new Assert\Length(array('min' => 10))); + + $metadata = $app['validator.mapping.class_metadata_factory']->getMetadataFor('Book'); + $metadata->addPropertyConstraint('title', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('author', new Assert\Valid()); + + $errors = $app['validator']->validate($book); + + if (count($errors) > 0) { + foreach ($errors as $error) { + echo $error->getPropertyPath().' '.$error->getMessage()."\n"; + } + } else { + echo 'The author is valid'; + } + +You can also declare the class constraint by adding a static +``loadValidatorMetadata`` method to your classes:: + + use Symfony\Component\Validator\Mapping\ClassMetadata; + use Symfony\Component\Validator\Constraints as Assert; + + class Book + { + public $title; + public $author; + + static public function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('title', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('author', new Assert\Valid()); + } + } + + class Author + { + public $first_name; + public $last_name; + + static public function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('first_name', new Assert\NotBlank()); + $metadata->addPropertyConstraint('first_name', new Assert\Length(array('min' => 10))); + $metadata->addPropertyConstraint('last_name', new Assert\Length(array('min' => 10))); + } + } + + $app->get('/validate/{email}', function ($email) use ($app) { + $author = new Author(); + $author->first_name = 'Fabien'; + $author->last_name = 'Potencier'; + + $book = new Book(); + $book->title = 'My Book'; + $book->author = $author; + + $errors = $app['validator']->validate($book); + + if (count($errors) > 0) { + foreach ($errors as $error) { + echo $error->getPropertyPath().' '.$error->getMessage()."\n"; + } + } else { + echo 'The author is valid'; + } + }); + +.. note:: + + Use ``addGetterConstraint()`` to add constraints on getter methods and + ``addConstraint()`` to add constraints on the class itself. + +Translation +~~~~~~~~~~~ + +To be able to translate the error messages, you can use the translator +provider and register the messages under the ``validators`` domain:: + + $app['translator.domains'] = array( + 'validators' => array( + 'fr' => array( + 'This value should be a valid number.' => 'Cette valeur doit être un nombre.', + ), + ), + ); + +For more information, consult the `Symfony Validation documentation +`_. diff --git a/vendor/silex/silex/doc/providers/var_dumper.rst b/vendor/silex/silex/doc/providers/var_dumper.rst new file mode 100644 index 00000000..ea4dd19a --- /dev/null +++ b/vendor/silex/silex/doc/providers/var_dumper.rst @@ -0,0 +1,44 @@ +Var Dumper +========== + +The *VarDumperServiceProvider* provides a mechanism that allows exploring then +dumping any PHP variable. + +Parameters +---------- + +* **var_dumper.dump_destination**: A stream URL where dumps should be written + to (defaults to ``null``). + +Services +-------- + +* n/a + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\VarDumperServiceProvider()); + +.. note:: + + Add the Symfony VarDumper Component as a dependency: + + .. code-block:: bash + + composer require symfony/var-dumper + +Usage +----- + +Adding the VarDumper component as a Composer dependency gives you access to the +``dump()`` PHP function anywhere in your code. + +If you are using Twig, it also provides a ``dump()`` Twig function and a +``dump`` Twig tag. + +The VarDumperServiceProvider is also useful when used with the Silex +WebProfiler as the dumps are made available in the web debug toolbar and in the +web profiler. diff --git a/vendor/silex/silex/doc/services.rst b/vendor/silex/silex/doc/services.rst new file mode 100644 index 00000000..8f36e943 --- /dev/null +++ b/vendor/silex/silex/doc/services.rst @@ -0,0 +1,264 @@ +Services +======== + +Silex is not only a framework, it is also a service container. It does this by +extending `Pimple `_ which provides a very simple +service container. + +Dependency Injection +-------------------- + +.. note:: + + You can skip this if you already know what Dependency Injection is. + +Dependency Injection is a design pattern where you pass dependencies to +services instead of creating them from within the service or relying on +globals. This generally leads to code that is decoupled, re-usable, flexible +and testable. + +Here is an example of a class that takes a ``User`` object and stores it as a +file in JSON format:: + + class JsonUserPersister + { + private $basePath; + + public function __construct($basePath) + { + $this->basePath = $basePath; + } + + public function persist(User $user) + { + $data = $user->getAttributes(); + $json = json_encode($data); + $filename = $this->basePath.'/'.$user->id.'.json'; + file_put_contents($filename, $json, LOCK_EX); + } + } + +In this simple example the dependency is the ``basePath`` property. It is +passed to the constructor. This means you can create several independent +instances with different base paths. Of course dependencies do not have to be +simple strings. More often they are in fact other services. + +A service container is responsible for creating and storing services. It can +recursively create dependencies of the requested services and inject them. It +does so lazily, which means a service is only created when you actually need it. + +Pimple +------ + +Pimple makes strong use of closures and implements the ArrayAccess interface. + +We will start off by creating a new instance of Pimple -- and because +``Silex\Application`` extends ``Pimple\Container`` all of this applies to Silex +as well:: + + $container = new Pimple\Container(); + +or:: + + $app = new Silex\Application(); + +Parameters +~~~~~~~~~~ + +You can set parameters (which are usually strings) by setting an array key on +the container:: + + $app['some_parameter'] = 'value'; + +The array key can be any value. By convention dots are used for namespacing:: + + $app['asset.host'] = 'http://cdn.mysite.com/'; + +Reading parameter values is possible with the same syntax:: + + echo $app['some_parameter']; + +Service definitions +~~~~~~~~~~~~~~~~~~~ + +Defining services is no different than defining parameters. You just set an +array key on the container to be a closure. However, when you retrieve the +service, the closure is executed. This allows for lazy service creation:: + + $app['some_service'] = function () { + return new Service(); + }; + +And to retrieve the service, use:: + + $service = $app['some_service']; + +On first invocation, this will create the service; the same instance will then +be returned on any subsequent access. + +Factory services +~~~~~~~~~~~~~~~~ + +If you want a different instance to be returned for each service access, wrap +the service definition with the ``factory()`` method:: + + $app['some_service'] = $app->factory(function () { + return new Service(); + }); + +Every time you call ``$app['some_service']``, a new instance of the service is +created. + +Access container from closure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In many cases you will want to access the service container from within a +service definition closure. For example when fetching services the current +service depends on. + +Because of this, the container is passed to the closure as an argument:: + + $app['some_service'] = function ($app) { + return new Service($app['some_other_service'], $app['some_service.config']); + }; + +Here you can see an example of Dependency Injection. ``some_service`` depends +on ``some_other_service`` and takes ``some_service.config`` as configuration +options. The dependency is only created when ``some_service`` is accessed, and +it is possible to replace either of the dependencies by simply overriding +those definitions. + +Going back to our initial example, here's how we could use the container +to manage its dependencies:: + + $app['user.persist_path'] = '/tmp/users'; + $app['user.persister'] = function ($app) { + return new JsonUserPersister($app['user.persist_path']); + }; + + +Protected closures +~~~~~~~~~~~~~~~~~~ + +Because the container sees closures as factories for services, it will always +execute them when reading them. + +In some cases you will however want to store a closure as a parameter, so that +you can fetch it and execute it yourself -- with your own arguments. + +This is why Pimple allows you to protect your closures from being executed, by +using the ``protect`` method:: + + $app['closure_parameter'] = $app->protect(function ($a, $b) { + return $a + $b; + }); + + // will not execute the closure + $add = $app['closure_parameter']; + + // calling it now + echo $add(2, 3); + +Note that protected closures do not get access to the container. + +Core services +------------- + +Silex defines a range of services. + +* **request_stack**: Controls the lifecycle of requests, an instance of + `RequestStack `_. + It gives you access to ``GET``, ``POST`` parameters and lots more! + + Example usage:: + + $id = $app['request_stack']->getCurrentRequest()->get('id'); + + A request is only available when a request is being served; you can only + access it from within a controller, an application before/after middlewares, + or an error handler. + +* **routes**: The `RouteCollection + `_ + that is used internally. You can add, modify, read routes. + +* **url_generator**: An instance of `UrlGenerator + `_, + using the `RouteCollection + `_ + that is provided through the ``routes`` service. It has a ``generate`` + method, which takes the route name as an argument, followed by an array of + route parameters. + +* **controllers**: The ``Silex\ControllerCollection`` that is used internally. + Check the :doc:`Internals chapter ` for more information. + +* **dispatcher**: The `EventDispatcher + `_ + that is used internally. It is the core of the Symfony system and is used + quite a bit by Silex. + +* **resolver**: The `ControllerResolver + `_ + that is used internally. It takes care of executing the controller with the + right arguments. + +* **kernel**: The `HttpKernel + `_ + that is used internally. The HttpKernel is the heart of Symfony, it takes a + Request as input and returns a Response as output. + +* **request_context**: The request context is a simplified representation of + the request that is used by the router and the URL generator. + +* **exception_handler**: The Exception handler is the default handler that is + used when you don't register one via the ``error()`` method or if your + handler does not return a Response. Disable it with + ``unset($app['exception_handler'])``. + +* **logger**: A `LoggerInterface `_ instance. By default, logging is + disabled as the value is set to ``null``. To enable logging you can either use + the :doc:`MonologServiceProvider ` or define your own ``logger`` service that + conforms to the PSR logger interface. + +Core traits +----------- + +* ``Silex\Application\UrlGeneratorTrait`` adds the following shortcuts: + + * **path**: Generates a path. + + * **url**: Generates an absolute URL. + + .. code-block:: php + + $app->path('homepage'); + $app->url('homepage'); + +Core parameters +--------------- + +* **request.http_port** (optional): Allows you to override the default port + for non-HTTPS URLs. If the current request is HTTP, it will always use the + current port. + + Defaults to 80. + + This parameter can be used when generating URLs. + +* **request.https_port** (optional): Allows you to override the default port + for HTTPS URLs. If the current request is HTTPS, it will always use the + current port. + + Defaults to 443. + + This parameter can be used when generating URLs. + +* **debug** (optional): Returns whether or not the application is running in + debug mode. + + Defaults to false. + +* **charset** (optional): The charset to use for Responses. + + Defaults to UTF-8. diff --git a/vendor/silex/silex/doc/testing.rst b/vendor/silex/silex/doc/testing.rst new file mode 100644 index 00000000..17f5f571 --- /dev/null +++ b/vendor/silex/silex/doc/testing.rst @@ -0,0 +1,222 @@ +Testing +======= + +Because Silex is built on top of Symfony, it is very easy to write functional +tests for your application. Functional tests are automated software tests that +ensure that your code is working correctly. They go through the user interface, +using a fake browser, and mimic the actions a user would do. + +Why +--- + +If you are not familiar with software tests, you may be wondering why you would +need this. Every time you make a change to your application, you have to test +it. This means going through all the pages and making sure they are still +working. Functional tests save you a lot of time, because they enable you to +test your application in usually under a second by running a single command. + +For more information on functional testing, unit testing, and automated +software tests in general, check out `PHPUnit +`_ and `Bulat Shakirzyanov's talk +on Clean Code `_. + +PHPUnit +------- + +`PHPUnit `_ is the de-facto +standard testing framework for PHP. It was built for writing unit tests, but it +can be used for functional tests too. You write tests by creating a new class, +that extends the ``PHPUnit_Framework_TestCase``. Your test cases are methods +prefixed with ``test``:: + + class ContactFormTest extends \PHPUnit_Framework_TestCase + { + public function testInitialPage() + { + ... + } + } + +In your test cases, you do assertions on the state of what you are testing. In +this case we are testing a contact form, so we would want to assert that the +page loaded correctly and contains our form:: + + public function testInitialPage() + { + $statusCode = ... + $pageContent = ... + + $this->assertEquals(200, $statusCode); + $this->assertContains('Contact us', $pageContent); + $this->assertContains('`_ +section of the PHPUnit documentation. + +WebTestCase +----------- + +Symfony provides a WebTestCase class that can be used to write functional +tests. The Silex version of this class is ``Silex\WebTestCase``, and you can +use it by making your test extend it:: + + use Silex\WebTestCase; + + class ContactFormTest extends WebTestCase + { + ... + } + +.. caution:: + + If you need to override the ``setUp()`` method, don't forget to call the + parent (``parent::setUp()``) to call the Silex default setup. + +.. note:: + + If you want to use the Symfony ``WebTestCase`` class you will need to + explicitly install its dependencies for your project: + + .. code-block:: bash + + composer require --dev symfony/browser-kit symfony/css-selector + +For your WebTestCase, you will have to implement a ``createApplication`` +method, which returns your application instance:: + + public function createApplication() + { + // app.php must return an Application instance + return require __DIR__.'/path/to/app.php'; + } + +Make sure you do **not** use ``require_once`` here, as this method will be +executed before every test. + +.. tip:: + + By default, the application behaves in the same way as when using it from a + browser. But when an error occurs, it is sometimes easier to get raw + exceptions instead of HTML pages. It is rather simple if you tweak the + application configuration in the ``createApplication()`` method like + follows:: + + public function createApplication() + { + $app = require __DIR__.'/path/to/app.php'; + $app['debug'] = true; + unset($app['exception_handler']); + + return $app; + } + +.. tip:: + + If your application use sessions, set ``session.test`` to ``true`` to + simulate sessions:: + + public function createApplication() + { + // ... + + $app['session.test'] = true; + + // ... + } + +The WebTestCase provides a ``createClient`` method. A client acts as a browser, +and allows you to interact with your application. Here's how it works:: + + public function testInitialPage() + { + $client = $this->createClient(); + $crawler = $client->request('GET', '/'); + + $this->assertTrue($client->getResponse()->isOk()); + $this->assertCount(1, $crawler->filter('h1:contains("Contact us")')); + $this->assertCount(1, $crawler->filter('form')); + ... + } + +There are several things going on here. You have both a ``Client`` and a +``Crawler``. + +You can also access the application through ``$this->app``. + +Client +~~~~~~ + +The client represents a browser. It holds your browsing history, cookies and +more. The ``request`` method allows you to make a request to a page on your +application. + +.. note:: + + You can find some documentation for it in `the client section of the + testing chapter of the Symfony documentation + `_. + +Crawler +~~~~~~~ + +The crawler allows you to inspect the content of a page. You can filter it +using CSS expressions and lots more. + +.. note:: + + You can find some documentation for it in `the crawler section of the testing + chapter of the Symfony documentation + `_. + +Configuration +------------- + +The suggested way to configure PHPUnit is to create a ``phpunit.xml.dist`` +file, a ``tests`` folder and your tests in +``tests/YourApp/Tests/YourTest.php``. The ``phpunit.xml.dist`` file should +look like this: + +.. code-block:: xml + + + + + + ./tests/ + + + + +Your ``tests/YourApp/Tests/YourTest.php`` should look like this:: + + namespace YourApp\Tests; + + use Silex\WebTestCase; + + class YourTest extends WebTestCase + { + public function createApplication() + { + return require __DIR__.'/../../../app.php'; + } + + public function testFooBar() + { + ... + } + } + +Now, when running ``phpunit`` on the command line, tests should run. diff --git a/vendor/silex/silex/doc/usage.rst b/vendor/silex/silex/doc/usage.rst new file mode 100644 index 00000000..724254ce --- /dev/null +++ b/vendor/silex/silex/doc/usage.rst @@ -0,0 +1,799 @@ +Usage +===== + +Installation +------------ + +If you want to get started fast, use the `Silex Skeleton`_: + +.. code-block:: bash + + composer create-project fabpot/silex-skeleton path/to/install "~2.0" + +If you want more flexibility, use Composer_ instead: + +.. code-block:: bash + + composer require silex/silex:~2.0 + +Web Server +---------- + +All examples in the documentation rely on a well-configured web server; read +the :doc:`webserver documentation` to check yours. + +Bootstrap +--------- + +To bootstrap Silex, all you need to do is require the ``vendor/autoload.php`` +file and create an instance of ``Silex\Application``. After your controller +definitions, call the ``run`` method on your application:: + + // web/index.php + require_once __DIR__.'/../vendor/autoload.php'; + + $app = new Silex\Application(); + + // ... definitions + + $app->run(); + +.. tip:: + + When developing a website, you might want to turn on the debug mode to + ease debugging:: + + $app['debug'] = true; + +.. tip:: + + If your application is hosted behind a reverse proxy at address ``$ip``, + and you want Silex to trust the ``X-Forwarded-For*`` headers, you will + need to run your application like this:: + + use Symfony\Component\HttpFoundation\Request; + + Request::setTrustedProxies(array($ip)); + $app->run(); + +Routing +------- + +In Silex you define a route and the controller that is called when that +route is matched. A route pattern consists of: + +* *Pattern*: The route pattern defines a path that points to a resource. The + pattern can include variable parts and you are able to set RegExp + requirements for them. + +* *Method*: One of the following HTTP methods: ``GET``, ``POST``, ``PUT``, + ``DELETE``, ``PATCH``, or ``OPTIONS``. This describes the interaction with + the resource. + +The controller is defined using a closure like this:: + + function () { + // ... do something + } + +The return value of the closure becomes the content of the page. + +Example GET Route +~~~~~~~~~~~~~~~~~ + +Here is an example definition of a ``GET`` route:: + + $blogPosts = array( + 1 => array( + 'date' => '2011-03-29', + 'author' => 'igorw', + 'title' => 'Using Silex', + 'body' => '...', + ), + ); + + $app->get('/blog', function () use ($blogPosts) { + $output = ''; + foreach ($blogPosts as $post) { + $output .= $post['title']; + $output .= '
'; + } + + return $output; + }); + +Visiting ``/blog`` will return a list of blog post titles. The ``use`` +statement means something different in this context. It tells the closure to +import the ``$blogPosts`` variable from the outer scope. This allows you to use +it from within the closure. + +Dynamic Routing +~~~~~~~~~~~~~~~ + +Now, you can create another controller for viewing individual blog posts:: + + $app->get('/blog/{id}', function (Silex\Application $app, $id) use ($blogPosts) { + if (!isset($blogPosts[$id])) { + $app->abort(404, "Post $id does not exist."); + } + + $post = $blogPosts[$id]; + + return "

{$post['title']}

". + "

{$post['body']}

"; + }); + +This route definition has a variable ``{id}`` part which is passed to the +closure. + +The current ``Application`` is automatically injected by Silex to the Closure +thanks to the type hinting. + +When the post does not exist, you are using ``abort()`` to stop the request +early. It actually throws an exception, which you will see how to handle later +on. + +Example POST Route +~~~~~~~~~~~~~~~~~~ + +POST routes signify the creation of a resource. An example for this is a +feedback form. You will use the ``mail`` function to send an e-mail:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $app->post('/feedback', function (Request $request) { + $message = $request->get('message'); + mail('feedback@yoursite.com', '[YourSite] Feedback', $message); + + return new Response('Thank you for your feedback!', 201); + }); + +It is pretty straightforward. + +.. note:: + + There is a :doc:`SwiftmailerServiceProvider ` + included that you can use instead of ``mail()``. + +The current ``request`` is automatically injected by Silex to the Closure +thanks to the type hinting. It is an instance of +Request_, so you can fetch variables using the request ``get`` method. + +Instead of returning a string you are returning an instance of Response_. +This allows setting an HTTP status code, in this case it is set to +``201 Created``. + +.. note:: + + Silex always uses a ``Response`` internally, it converts strings to + responses with status code ``200``. + +Other methods +~~~~~~~~~~~~~ + +You can create controllers for most HTTP methods. Just call one of these +methods on your application: ``get``, ``post``, ``put``, ``delete``, ``patch``, ``options``:: + + $app->put('/blog/{id}', function ($id) { + // ... + }); + + $app->delete('/blog/{id}', function ($id) { + // ... + }); + + $app->patch('/blog/{id}', function ($id) { + // ... + }); + +.. tip:: + + Forms in most web browsers do not directly support the use of other HTTP + methods. To use methods other than GET and POST you can utilize a special + form field with a name of ``_method``. The form's ``method`` attribute must + be set to POST when using this field: + + .. code-block:: html + +
+ + +
+ + You need to explicitly enable this method override:: + + use Symfony\Component\HttpFoundation\Request; + + Request::enableHttpMethodParameterOverride(); + $app->run(); + +You can also call ``match``, which will match all methods. This can be +restricted via the ``method`` method:: + + $app->match('/blog', function () { + // ... + }); + + $app->match('/blog', function () { + // ... + }) + ->method('PATCH'); + + $app->match('/blog', function () { + // ... + }) + ->method('PUT|POST'); + +.. note:: + + The order in which the routes are defined is significant. The first + matching route will be used, so place more generic routes at the bottom. + +Route Variables +~~~~~~~~~~~~~~~ + +As it has been shown before you can define variable parts in a route like +this:: + + $app->get('/blog/{id}', function ($id) { + // ... + }); + +It is also possible to have more than one variable part, just make sure the +closure arguments match the names of the variable parts:: + + $app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) { + // ... + }); + +While it's not recommended, you could also do this (note the switched +arguments):: + + $app->get('/blog/{postId}/{commentId}', function ($commentId, $postId) { + // ... + }); + +You can also ask for the current Request and Application objects:: + + $app->get('/blog/{id}', function (Application $app, Request $request, $id) { + // ... + }); + +.. note:: + + Note for the Application and Request objects, Silex does the injection + based on the type hinting and not on the variable name:: + + $app->get('/blog/{id}', function (Application $foo, Request $bar, $id) { + // ... + }); + +Route Variable Converters +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before injecting the route variables into the controller, you can apply some +converters:: + + $app->get('/user/{id}', function ($id) { + // ... + })->convert('id', function ($id) { return (int) $id; }); + +This is useful when you want to convert route variables to objects as it +allows to reuse the conversion code across different controllers:: + + $userProvider = function ($id) { + return new User($id); + }; + + $app->get('/user/{user}', function (User $user) { + // ... + })->convert('user', $userProvider); + + $app->get('/user/{user}/edit', function (User $user) { + // ... + })->convert('user', $userProvider); + +The converter callback also receives the ``Request`` as its second argument:: + + $callback = function ($post, Request $request) { + return new Post($request->attributes->get('slug')); + }; + + $app->get('/blog/{id}/{slug}', function (Post $post) { + // ... + })->convert('post', $callback); + +A converter can also be defined as a service. For example, here is a user +converter based on Doctrine ObjectManager:: + + use Doctrine\Common\Persistence\ObjectManager; + use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + + class UserConverter + { + private $om; + + public function __construct(ObjectManager $om) + { + $this->om = $om; + } + + public function convert($id) + { + if (null === $user = $this->om->find('User', (int) $id)) { + throw new NotFoundHttpException(sprintf('User %d does not exist', $id)); + } + + return $user; + } + } + +The service will now be registered in the application, and the +``convert()`` method will be used as converter (using the syntax +``service_name:method_name``):: + + $app['converter.user'] = function () { + return new UserConverter(); + }; + + $app->get('/user/{user}', function (User $user) { + // ... + })->convert('user', 'converter.user:convert'); + +Requirements +~~~~~~~~~~~~ + +In some cases you may want to only match certain expressions. You can define +requirements using regular expressions by calling ``assert`` on the +``Controller`` object, which is returned by the routing methods. + +The following will make sure the ``id`` argument is a positive integer, since +``\d+`` matches any amount of digits:: + + $app->get('/blog/{id}', function ($id) { + // ... + }) + ->assert('id', '\d+'); + +You can also chain these calls:: + + $app->get('/blog/{postId}/{commentId}', function ($postId, $commentId) { + // ... + }) + ->assert('postId', '\d+') + ->assert('commentId', '\d+'); + +Conditions +~~~~~~~~~~ + +Besides restricting route matching based on the HTTP method or parameter +requirements, you can set conditions on any part of the request by calling +``when`` on the ``Controller`` object, which is returned by the routing +methods:: + + $app->get('/blog/{id}', function ($id) { + // ... + }) + ->when("request.headers.get('User-Agent') matches '/firefox/i'"); + +The ``when`` argument is a Symfony Expression_ , which means that you need to +add ``symfony/expression-language`` as a dependency of your project. + +Default Values +~~~~~~~~~~~~~~ + +You can define a default value for any route variable by calling ``value`` on +the ``Controller`` object:: + + $app->get('/{pageName}', function ($pageName) { + // ... + }) + ->value('pageName', 'index'); + +This will allow matching ``/``, in which case the ``pageName`` variable will +have the value ``index``. + +Named Routes +~~~~~~~~~~~~ + +Some providers can make use of named routes. By default Silex will generate an +internal route name for you but you can give an explicit route name by calling +``bind``:: + + $app->get('/', function () { + // ... + }) + ->bind('homepage'); + + $app->get('/blog/{id}', function ($id) { + // ... + }) + ->bind('blog_post'); + +Controllers as Classes +~~~~~~~~~~~~~~~~~~~~~~ + +Instead of anonymous functions, you can also define your controllers as +methods. By using the ``ControllerClass::methodName`` syntax, you can tell +Silex to lazily create the controller object for you:: + + $app->get('/', 'Acme\\Foo::bar'); + + use Silex\Application; + use Symfony\Component\HttpFoundation\Request; + + namespace Acme + { + class Foo + { + public function bar(Request $request, Application $app) + { + // ... + } + } + } + +This will load the ``Acme\Foo`` class on demand, create an instance and call +the ``bar`` method to get the response. You can use ``Request`` and +``Silex\Application`` type hints to get ``$request`` and ``$app`` injected. + +It is also possible to :doc:`define your controllers as services +`. + +Global Configuration +-------------------- + +If a controller setting must be applied to **all** controllers (a converter, a +middleware, a requirement, or a default value), configure it on +``$app['controllers']``, which holds all application controllers:: + + $app['controllers'] + ->value('id', '1') + ->assert('id', '\d+') + ->requireHttps() + ->method('get') + ->convert('id', function () { /* ... */ }) + ->before(function () { /* ... */ }) + ->when('request.isSecure() == true') + ; + +These settings are applied to already registered controllers and they become +the defaults for new controllers. + +.. note:: + + The global configuration does not apply to controller providers you might + mount as they have their own global configuration (read the + :doc:`dedicated chapter` for more information). + +Error Handlers +-------------- + +When an exception is thrown, error handlers allow you to display a custom +error page to the user. They can also be used to do additional things, such as +logging. + +To register an error handler, pass a closure to the ``error`` method which +takes an ``Exception`` argument and returns a response:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\Exception $e, Request $request, $code) { + return new Response('We are sorry, but something went terribly wrong.'); + }); + +You can also check for specific errors by using the ``$code`` argument, and +handle them differently:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\Exception $e, Request $request, $code) { + switch ($code) { + case 404: + $message = 'The requested page could not be found.'; + break; + default: + $message = 'We are sorry, but something went terribly wrong.'; + } + + return new Response($message); + }); + +You can restrict an error handler to only handle some Exception classes by +setting a more specific type hint for the Closure argument:: + + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\LogicException $e, Request $request, $code) { + // this handler will only handle \LogicException exceptions + // and exceptions that extend \LogicException + }); + +.. note:: + + As Silex ensures that the Response status code is set to the most + appropriate one depending on the exception, setting the status on the + response won't work. If you want to overwrite the status code, set the + ``X-Status-Code`` header:: + + return new Response('Error', 404 /* ignored */, array('X-Status-Code' => 200)); + +If you want to use a separate error handler for logging, make sure you register +it with a higher priority than response error handlers, because once a response +is returned, the following handlers are ignored. + +.. note:: + + Silex ships with a provider for Monolog_ which handles logging of errors. + Check out the *Providers* :doc:`chapter ` for details. + +.. tip:: + + Silex comes with a default error handler that displays a detailed error + message with the stack trace when **debug** is true, and a simple error + message otherwise. Error handlers registered via the ``error()`` method + always take precedence but you can keep the nice error messages when debug + is turned on like this:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + + $app->error(function (\Exception $e, Request $request, $code) use ($app) { + if ($app['debug']) { + return; + } + + // ... logic to handle the error and return a Response + }); + +The error handlers are also called when you use ``abort`` to abort a request +early:: + + $app->get('/blog/{id}', function (Silex\Application $app, $id) use ($blogPosts) { + if (!isset($blogPosts[$id])) { + $app->abort(404, "Post $id does not exist."); + } + + return new Response(...); + }); + +You can convert errors to ``Exceptions``, check out the cookbook :doc:`chapter ` for details. + +View Handlers +------------- + +View Handlers allow you to intercept a controller result that is not a +``Response`` and transform it before it gets returned to the kernel. + +To register a view handler, pass a callable (or string that can be resolved to a +callable) to the ``view()`` method. The callable should accept some sort of result +from the controller:: + + $app->view(function (array $controllerResult) use ($app) { + return $app->json($controllerResult); + }); + +View Handlers also receive the ``Request`` as their second argument, +making them a good candidate for basic content negotiation:: + + $app->view(function (array $controllerResult, Request $request) use ($app) { + $acceptHeader = $request->headers->get('Accept'); + $bestFormat = $app['negotiator']->getBestFormat($acceptHeader, array('json', 'xml')); + + if ('json' === $bestFormat) { + return new JsonResponse($controllerResult); + } + + if ('xml' === $bestFormat) { + return $app['serializer.xml']->renderResponse($controllerResult); + } + + return $controllerResult; + }); + +View Handlers will be examined in the order they are added to the application +and Silex will use type hints to determine if a view handler should be used for +the current result, continuously using the return value of the last view handler +as the input for the next. + +.. note:: + + You must ensure that Silex receives a ``Response`` or a string as the result of + the last view handler (or controller) to be run. + +Redirects +--------- + +You can redirect to another page by returning a ``RedirectResponse`` response, +which you can create by calling the ``redirect`` method:: + + $app->get('/', function () use ($app) { + return $app->redirect('/hello'); + }); + +This will redirect from ``/`` to ``/hello``. + +Forwards +-------- + +When you want to delegate the rendering to another controller, without a +round-trip to the browser (as for a redirect), use an internal sub-request:: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpKernel\HttpKernelInterface; + + $app->get('/', function () use ($app) { + // forward to /hello + $subRequest = Request::create('/hello', 'GET'); + + return $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + }); + +.. tip:: + + You can also generate the URI via the built-in URL generator:: + + $request = Request::create($app['url_generator']->generate('hello'), 'GET'); + +There's some more things that you need to keep in mind though. In most cases you +will want to forward some parts of the current master request to the sub-request. +That includes: Cookies, server information, session. +Read more on :doc:`how to make sub-requests `. + +JSON +---- + +If you want to return JSON data, you can use the ``json`` helper method. +Simply pass it your data, status code and headers, and it will create a JSON +response for you:: + + $app->get('/users/{id}', function ($id) use ($app) { + $user = getUser($id); + + if (!$user) { + $error = array('message' => 'The user was not found.'); + + return $app->json($error, 404); + } + + return $app->json($user); + }); + +Streaming +--------- + +It's possible to stream a response, which is important in cases when you don't +want to buffer the data being sent:: + + $app->get('/images/{file}', function ($file) use ($app) { + if (!file_exists(__DIR__.'/images/'.$file)) { + return $app->abort(404, 'The image was not found.'); + } + + $stream = function () use ($file) { + readfile($file); + }; + + return $app->stream($stream, 200, array('Content-Type' => 'image/png')); + }); + +If you need to send chunks, make sure you call ``ob_flush`` and ``flush`` +after every chunk:: + + $stream = function () { + $fh = fopen('http://www.example.com/', 'rb'); + while (!feof($fh)) { + echo fread($fh, 1024); + ob_flush(); + flush(); + } + fclose($fh); + }; + +Sending a file +-------------- + +If you want to return a file, you can use the ``sendFile`` helper method. +It eases returning files that would otherwise not be publicly available. Simply +pass it your file path, status code, headers and the content disposition and it +will create a ``BinaryFileResponse`` response for you:: + + $app->get('/files/{path}', function ($path) use ($app) { + if (!file_exists('/base/path/' . $path)) { + $app->abort(404); + } + + return $app->sendFile('/base/path/' . $path); + }); + +To further customize the response before returning it, check the API doc for +`Symfony\Component\HttpFoundation\BinaryFileResponse +`_:: + + return $app + ->sendFile('/base/path/' . $path) + ->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'pic.jpg') + ; + +Traits +------ + +Silex comes with PHP traits that define shortcut methods. + +Almost all built-in service providers have some corresponding PHP traits. To +use them, define your own Application class and include the traits you want:: + + use Silex\Application; + + class MyApplication extends Application + { + use Application\TwigTrait; + use Application\SecurityTrait; + use Application\FormTrait; + use Application\UrlGeneratorTrait; + use Application\SwiftmailerTrait; + use Application\MonologTrait; + use Application\TranslationTrait; + } + +You can also define your own Route class and use some traits:: + + use Silex\Route; + + class MyRoute extends Route + { + use Route\SecurityTrait; + } + +To use your newly defined route, override the ``$app['route_class']`` +setting:: + + $app['route_class'] = 'MyRoute'; + +Read each provider chapter to learn more about the added methods. + +Security +-------- + +Make sure to protect your application against attacks. + +Escaping +~~~~~~~~ + +When outputting any user input, make sure to escape it correctly to prevent +Cross-Site-Scripting attacks. + +* **Escaping HTML**: PHP provides the ``htmlspecialchars`` function for this. + Silex provides a shortcut ``escape`` method:: + + use Symfony\Component\HttpFoundation\Request; + + $app->get('/name', function (Request $request, Silex\Application $app) { + $name = $request->get('name'); + + return "You provided the name {$app->escape($name)}."; + }); + + If you use the Twig template engine, you should use its escaping or even + auto-escaping mechanisms. Check out the *Providers* :doc:`chapter ` for details. + +* **Escaping JSON**: If you want to provide data in JSON format you should + use the Silex ``json`` function:: + + use Symfony\Component\HttpFoundation\Request; + + $app->get('/name.json', function (Request $request, Silex\Application $app) { + $name = $request->get('name'); + + return $app->json(array('name' => $name)); + }); + +.. _Silex Skeleton: http://github.com/silexphp/Silex-Skeleton +.. _Composer: http://getcomposer.org/ +.. _Request: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Request.html +.. _Response: http://api.symfony.com/master/Symfony/Component/HttpFoundation/Response.html +.. _Monolog: https://github.com/Seldaek/monolog +.. _Expression: https://symfony.com/doc/current/book/routing.html#completely-customized-route-matching-with-conditions diff --git a/vendor/silex/silex/doc/web_servers.rst b/vendor/silex/silex/doc/web_servers.rst new file mode 100644 index 00000000..4fd2dc74 --- /dev/null +++ b/vendor/silex/silex/doc/web_servers.rst @@ -0,0 +1,183 @@ +Webserver Configuration +======================= + +Apache +------ + +If you are using Apache, make sure ``mod_rewrite`` is enabled and use the +following ``.htaccess`` file: + +.. code-block:: apache + + + Options -MultiViews + + RewriteEngine On + #RewriteBase /path/to/app + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [QSA,L] + + +.. note:: + + If your site is not at the webroot level you will have to uncomment the + ``RewriteBase`` statement and adjust the path to point to your directory, + relative from the webroot. + +Alternatively, if you use Apache 2.2.16 or higher, you can use the +`FallbackResource directive`_ to make your .htaccess even easier: + +.. code-block:: apache + + FallbackResource index.php + +.. note:: + + If your site is not at the webroot level you will have to adjust the path to + point to your directory, relative from the webroot. + +Or if you're using a VirtualHost, you can add the same directive to the VirtualHost's Directory entry: + +.. code-block:: apache + + + # other directives + + + # other directives + + FallbackResource /index.php + + + +.. note:: + + Note that you need the leading forward slash there, unlike with the .htaccess version + +nginx +----- + +The **minimum configuration** to get your application running under Nginx is: + +.. code-block:: nginx + + server { + server_name domain.tld www.domain.tld; + root /var/www/project/web; + + location / { + # try to serve file directly, fallback to front controller + try_files $uri /index.php$is_args$args; + } + + # If you have 2 front controllers for dev|prod use the following line instead + # location ~ ^/(index|index_dev)\.php(/|$) { + location ~ ^/index\.php(/|$) { + # the ubuntu default + fastcgi_pass unix:/var/run/php/phpX.X-fpm.sock; + # for running on centos + #fastcgi_pass unix:/var/run/php-fpm/www.sock; + + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS off; + + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/index.php/some-path + # Enable the internal directive to disable URIs like this + # internal; + } + + #return 404 for all php files as we do have a front controller + location ~ \.php$ { + return 404; + } + + error_log /var/log/nginx/project_error.log; + access_log /var/log/nginx/project_access.log; + } + +IIS +--- + +If you are using the Internet Information Services from Windows, you can use +this sample ``web.config`` file: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + + +Lighttpd +-------- + +If you are using lighttpd, use this sample ``simple-vhost`` as a starting +point: + +.. code-block:: lighttpd + + server.document-root = "/path/to/app" + + url.rewrite-once = ( + # configure some static files + "^/assets/.+" => "$0", + "^/favicon\.ico$" => "$0", + + "^(/[^\?]*)(\?.*)?" => "/index.php$1$2" + ) + +.. _FallbackResource directive: http://www.adayinthelifeof.nl/2012/01/21/apaches-fallbackresource-your-new-htaccess-command/ + +PHP +--- + +PHP ships with a built-in webserver for development. This server allows you to +run silex without any configuration. However, in order to serve static files, +you'll have to make sure your front controller returns false in that case:: + + // web/index.php + + $filename = __DIR__.preg_replace('#(\?.*)$#', '', $_SERVER['REQUEST_URI']); + if (php_sapi_name() === 'cli-server' && is_file($filename)) { + return false; + } + + $app = require __DIR__.'/../src/app.php'; + $app->run(); + + +Assuming your front controller is at ``web/index.php``, you can start the +server from the command-line with this command: + +.. code-block:: text + + $ php -S localhost:8080 -t web web/index.php + +Now the application should be running at ``http://localhost:8080``. + +.. note:: + + This server is for development only. It is **not** recommended to use it + in production. diff --git a/vendor/silex/silex/phpunit.xml.dist b/vendor/silex/silex/phpunit.xml.dist new file mode 100644 index 00000000..799f16c9 --- /dev/null +++ b/vendor/silex/silex/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + ./tests/Silex/ + + + + + ./src + + + diff --git a/vendor/silex/silex/src/Silex/Api/BootableProviderInterface.php b/vendor/silex/silex/src/Silex/Api/BootableProviderInterface.php new file mode 100644 index 00000000..739e04d5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/BootableProviderInterface.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 Silex\Api; + +use Silex\Application; + +/** + * Interface for bootable service providers. + * + * @author Fabien Potencier + */ +interface BootableProviderInterface +{ + /** + * Bootstraps the application. + * + * This method is called after all services are registered + * and should be used for "dynamic" configuration (whenever + * a service must be requested). + * + * @param Application $app + */ + public function boot(Application $app); +} diff --git a/vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.php b/vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.php new file mode 100644 index 00000000..28d9d0e5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/ControllerProviderInterface.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 Silex\Api; + +use Silex\Application; +use Silex\ControllerCollection; + +/** + * Interface for controller providers. + * + * @author Fabien Potencier + */ +interface ControllerProviderInterface +{ + /** + * Returns routes to connect to the given application. + * + * @param Application $app An Application instance + * + * @return ControllerCollection A ControllerCollection instance + */ + public function connect(Application $app); +} diff --git a/vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php b/vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php new file mode 100644 index 00000000..f3e62555 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/EventListenerProviderInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Api; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Pimple\Container; + +/** + * Interface for event listener providers. + * + * @author Fabien Potencier + */ +interface EventListenerProviderInterface +{ + public function subscribe(Container $app, EventDispatcherInterface $dispatcher); +} diff --git a/vendor/silex/silex/src/Silex/Api/LICENSE b/vendor/silex/silex/src/Silex/Api/LICENSE new file mode 100644 index 00000000..bc6ad049 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2015 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/silex/silex/src/Silex/Api/composer.json b/vendor/silex/silex/src/Silex/Api/composer.json new file mode 100644 index 00000000..2cb90bcd --- /dev/null +++ b/vendor/silex/silex/src/Silex/Api/composer.json @@ -0,0 +1,34 @@ +{ + "minimum-stability": "dev", + "name": "silex/api", + "description": "The Silex interfaces", + "keywords": ["microframework"], + "homepage": "http://silex.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0" + }, + "suggest": { + "symfony/event-dispatcher": "For EventListenerProviderInterface", + "silex/silex": "For BootableProviderInterface and ControllerProviderInterface" + }, + "autoload": { + "psr-4": { "Silex\\Api\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + } +} diff --git a/vendor/silex/silex/src/Silex/AppArgumentValueResolver.php b/vendor/silex/silex/src/Silex/AppArgumentValueResolver.php new file mode 100644 index 00000000..cc2197ab --- /dev/null +++ b/vendor/silex/silex/src/Silex/AppArgumentValueResolver.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * HttpKernel Argument Resolver for Silex. + * + * @author Romain Neutron + */ +class AppArgumentValueResolver implements ArgumentValueResolverInterface +{ + private $app; + + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return null !== $argument->getType() && ($argument->getType() === Application::class || is_subclass_of($argument->getType(), Application::class)); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $this->app; + } +} diff --git a/vendor/silex/silex/src/Silex/Application.php b/vendor/silex/silex/src/Silex/Application.php new file mode 100644 index 00000000..07227682 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application.php @@ -0,0 +1,506 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\JsonResponse; +use Silex\Api\BootableProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Api\ControllerProviderInterface; +use Silex\Provider\ExceptionHandlerServiceProvider; +use Silex\Provider\RoutingServiceProvider; +use Silex\Provider\HttpKernelServiceProvider; + +/** + * The Silex framework class. + * + * @author Fabien Potencier + */ +class Application extends Container implements HttpKernelInterface, TerminableInterface +{ + const VERSION = '2.1.0'; + + const EARLY_EVENT = 512; + const LATE_EVENT = -512; + + protected $providers = array(); + protected $booted = false; + + /** + * Instantiate a new Application. + * + * Objects and parameters can be passed as argument to the constructor. + * + * @param array $values The parameters or objects. + */ + public function __construct(array $values = array()) + { + parent::__construct(); + + $this['request.http_port'] = 80; + $this['request.https_port'] = 443; + $this['debug'] = false; + $this['charset'] = 'UTF-8'; + $this['logger'] = null; + + $this->register(new HttpKernelServiceProvider()); + $this->register(new RoutingServiceProvider()); + $this->register(new ExceptionHandlerServiceProvider()); + + foreach ($values as $key => $value) { + $this[$key] = $value; + } + } + + /** + * Registers a service provider. + * + * @param ServiceProviderInterface $provider A ServiceProviderInterface instance + * @param array $values An array of values that customizes the provider + * + * @return Application + */ + public function register(ServiceProviderInterface $provider, array $values = array()) + { + $this->providers[] = $provider; + + parent::register($provider, $values); + + return $this; + } + + /** + * Boots all service providers. + * + * This method is automatically called by handle(), but you can use it + * to boot all service providers when not handling a request. + */ + public function boot() + { + if ($this->booted) { + return; + } + + $this->booted = true; + + foreach ($this->providers as $provider) { + if ($provider instanceof EventListenerProviderInterface) { + $provider->subscribe($this, $this['dispatcher']); + } + + if ($provider instanceof BootableProviderInterface) { + $provider->boot($this); + } + } + } + + /** + * Maps a pattern to a callable. + * + * You can optionally specify HTTP methods that should be matched. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function match($pattern, $to = null) + { + return $this['controllers']->match($pattern, $to); + } + + /** + * Maps a GET request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function get($pattern, $to = null) + { + return $this['controllers']->get($pattern, $to); + } + + /** + * Maps a POST request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function post($pattern, $to = null) + { + return $this['controllers']->post($pattern, $to); + } + + /** + * Maps a PUT request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function put($pattern, $to = null) + { + return $this['controllers']->put($pattern, $to); + } + + /** + * Maps a DELETE request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function delete($pattern, $to = null) + { + return $this['controllers']->delete($pattern, $to); + } + + /** + * Maps an OPTIONS request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function options($pattern, $to = null) + { + return $this['controllers']->options($pattern, $to); + } + + /** + * Maps a PATCH request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function patch($pattern, $to = null) + { + return $this['controllers']->patch($pattern, $to); + } + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $callback The listener + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function on($eventName, $callback, $priority = 0) + { + if ($this->booted) { + $this['dispatcher']->addListener($eventName, $this['callback_resolver']->resolveCallback($callback), $priority); + + return; + } + + $this->extend('dispatcher', function (EventDispatcherInterface $dispatcher, $app) use ($callback, $priority, $eventName) { + $dispatcher->addListener($eventName, $app['callback_resolver']->resolveCallback($callback), $priority); + + return $dispatcher; + }); + } + + /** + * Registers a before filter. + * + * Before filters are run before any route has been matched. + * + * @param mixed $callback Before filter callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function before($callback, $priority = 0) + { + $app = $this; + + $this->on(KernelEvents::REQUEST, function (GetResponseEvent $event) use ($callback, $app) { + if (!$event->isMasterRequest()) { + return; + } + + $ret = call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $app); + + if ($ret instanceof Response) { + $event->setResponse($ret); + } + }, $priority); + } + + /** + * Registers an after filter. + * + * After filters are run after the controller has been executed. + * + * @param mixed $callback After filter callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function after($callback, $priority = 0) + { + $app = $this; + + $this->on(KernelEvents::RESPONSE, function (FilterResponseEvent $event) use ($callback, $app) { + if (!$event->isMasterRequest()) { + return; + } + + $response = call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $event->getResponse(), $app); + if ($response instanceof Response) { + $event->setResponse($response); + } elseif (null !== $response) { + throw new \RuntimeException('An after middleware returned an invalid response value. Must return null or an instance of Response.'); + } + }, $priority); + } + + /** + * Registers a finish filter. + * + * Finish filters are run after the response has been sent. + * + * @param mixed $callback Finish filter callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function finish($callback, $priority = 0) + { + $app = $this; + + $this->on(KernelEvents::TERMINATE, function (PostResponseEvent $event) use ($callback, $app) { + call_user_func($app['callback_resolver']->resolveCallback($callback), $event->getRequest(), $event->getResponse(), $app); + }, $priority); + } + + /** + * Aborts the current request by sending a proper HTTP error. + * + * @param int $statusCode The HTTP status code + * @param string $message The status message + * @param array $headers An array of HTTP headers + */ + public function abort($statusCode, $message = '', array $headers = array()) + { + throw new HttpException($statusCode, $message, null, $headers); + } + + /** + * Registers an error handler. + * + * Error handlers are simple callables which take a single Exception + * as an argument. If a controller throws an exception, an error handler + * can return a specific response. + * + * When an exception occurs, all handlers will be called, until one returns + * something (a string or a Response object), at which point that will be + * returned to the client. + * + * For this reason you should add logging handlers before output handlers. + * + * @param mixed $callback Error handler callback, takes an Exception argument + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to -8) + */ + public function error($callback, $priority = -8) + { + $this->on(KernelEvents::EXCEPTION, new ExceptionListenerWrapper($this, $callback), $priority); + } + + /** + * Registers a view handler. + * + * View handlers are simple callables which take a controller result and the + * request as arguments, whenever a controller returns a value that is not + * an instance of Response. When this occurs, all suitable handlers will be + * called, until one returns a Response object. + * + * @param mixed $callback View handler callback + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function view($callback, $priority = 0) + { + $this->on(KernelEvents::VIEW, new ViewListenerWrapper($this, $callback), $priority); + } + + /** + * Flushes the controller collection. + */ + public function flush() + { + $this['routes']->addCollection($this['controllers']->flush()); + } + + /** + * Redirects the user to another URL. + * + * @param string $url The URL to redirect to + * @param int $status The status code (302 by default) + * + * @return RedirectResponse + */ + public function redirect($url, $status = 302) + { + return new RedirectResponse($url, $status); + } + + /** + * Creates a streaming response. + * + * @param mixed $callback A valid PHP callback + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return StreamedResponse + */ + public function stream($callback = null, $status = 200, array $headers = array()) + { + return new StreamedResponse($callback, $status, $headers); + } + + /** + * Escapes a text for HTML. + * + * @param string $text The input text to be escaped + * @param int $flags The flags (@see htmlspecialchars) + * @param string $charset The charset + * @param bool $doubleEncode Whether to try to avoid double escaping or not + * + * @return string Escaped text + */ + public function escape($text, $flags = ENT_COMPAT, $charset = null, $doubleEncode = true) + { + return htmlspecialchars($text, $flags, $charset ?: $this['charset'], $doubleEncode); + } + + /** + * Convert some data into a JSON response. + * + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return JsonResponse + */ + public function json($data = array(), $status = 200, array $headers = array()) + { + return new JsonResponse($data, $status, $headers); + } + + /** + * Sends a file. + * + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename + * + * @return BinaryFileResponse + */ + public function sendFile($file, $status = 200, array $headers = array(), $contentDisposition = null) + { + return new BinaryFileResponse($file, $status, $headers, true, $contentDisposition); + } + + /** + * Mounts controllers under the given route prefix. + * + * @param string $prefix The route prefix + * @param ControllerCollection|callable|ControllerProviderInterface $controllers A ControllerCollection, a callable, or a ControllerProviderInterface instance + * + * @return Application + * + * @throws \LogicException + */ + public function mount($prefix, $controllers) + { + if ($controllers instanceof ControllerProviderInterface) { + $connectedControllers = $controllers->connect($this); + + if (!$connectedControllers instanceof ControllerCollection) { + throw new \LogicException(sprintf('The method "%s::connect" must return a "ControllerCollection" instance. Got: "%s"', get_class($controllers), is_object($connectedControllers) ? get_class($connectedControllers) : gettype($connectedControllers))); + } + + $controllers = $connectedControllers; + } elseif (!$controllers instanceof ControllerCollection && !is_callable($controllers)) { + throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable.'); + } + + $this['controllers']->mount($prefix, $controllers); + + return $this; + } + + /** + * Handles the request and delivers the response. + * + * @param Request|null $request Request to process + */ + public function run(Request $request = null) + { + if (null === $request) { + $request = Request::createFromGlobals(); + } + + $response = $this->handle($request); + $response->send(); + $this->terminate($request, $response); + } + + /** + * {@inheritdoc} + * + * If you call this method directly instead of run(), you must call the + * terminate() method yourself if you want the finish filters to be run. + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + if (!$this->booted) { + $this->boot(); + } + + $this->flush(); + + return $this['kernel']->handle($request, $type, $catch); + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + $this['kernel']->terminate($request, $response); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/FormTrait.php b/vendor/silex/silex/src/Silex/Application/FormTrait.php new file mode 100644 index 00000000..2eeb23e4 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/FormTrait.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +use Symfony\Component\Form; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilder; +use Symfony\Component\OptionsResolver\OptionsResolver\FormTypeInterface; + +/** + * Form trait. + * + * @author Fabien Potencier + * @author David Berlioz + */ +trait FormTrait +{ + /** + * Creates and returns a form builder instance. + * + * @param mixed $data The initial data for the form + * @param array $options Options for the form + * @param string|FormTypeInterface $type Type of the form + * + * @return FormBuilder + */ + public function form($data = null, array $options = array(), $type = null) + { + return $this['form.factory']->createBuilder($type ?: FormType::class, $data, $options); + } + + /** + * Creates and returns a named form builder instance. + * + * @param string $name + * @param mixed $data The initial data for the form + * @param array $options Options for the form + * @param string|FormTypeInterface $type Type of the form + * + * @return FormBuilder + */ + public function namedForm($name, $data = null, array $options = array(), $type = null) + { + return $this['form.factory']->createNamedBuilder($name, $type ?: FormType::class, $data, $options); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/MonologTrait.php b/vendor/silex/silex/src/Silex/Application/MonologTrait.php new file mode 100644 index 00000000..18cb54c6 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/MonologTrait.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 Silex\Application; + +use Monolog\Logger; + +/** + * Monolog trait. + * + * @author Fabien Potencier + */ +trait MonologTrait +{ + /** + * Adds a log record. + * + * @param string $message The log message + * @param array $context The log context + * @param int $level The logging level + * + * @return bool Whether the record has been processed + */ + public function log($message, array $context = array(), $level = Logger::INFO) + { + return $this['monolog']->addRecord($level, $message, $context); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/SecurityTrait.php b/vendor/silex/silex/src/Silex/Application/SecurityTrait.php new file mode 100644 index 00000000..43ce5552 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/SecurityTrait.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 Silex\Application; + +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Security trait. + * + * @author Fabien Potencier + */ +trait SecurityTrait +{ + /** + * Encodes the raw password. + * + * @param UserInterface $user A UserInterface instance + * @param string $password The password to encode + * + * @return string The encoded password + * + * @throws \RuntimeException when no password encoder could be found for the user + */ + public function encodePassword(UserInterface $user, $password) + { + return $this['security.encoder_factory']->getEncoder($user)->encodePassword($password, $user->getSalt()); + } + + /** + * Checks if the attributes are granted against the current authentication token and optionally supplied object. + * + * @param mixed $attributes + * @param mixed $object + * + * @return bool + * + * @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token. + */ + public function isGranted($attributes, $object = null) + { + return $this['security.authorization_checker']->isGranted($attributes, $object); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.php b/vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.php new file mode 100644 index 00000000..157f94d8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/SwiftmailerTrait.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 Silex\Application; + +/** + * Swiftmailer trait. + * + * @author Fabien Potencier + */ +trait SwiftmailerTrait +{ + /** + * Sends an email. + * + * @param \Swift_Message $message A \Swift_Message instance + * @param array $failedRecipients An array of failures by-reference + * + * @return int The number of sent messages + */ + public function mail(\Swift_Message $message, &$failedRecipients = null) + { + return $this['mailer']->send($message, $failedRecipients); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/TranslationTrait.php b/vendor/silex/silex/src/Silex/Application/TranslationTrait.php new file mode 100644 index 00000000..8b6e818e --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/TranslationTrait.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 Silex\Application; + +/** + * Translation trait. + * + * @author Fabien Potencier + */ +trait TranslationTrait +{ + /** + * Translates the given message. + * + * @param string $id The message id + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + */ + public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this['translator']->trans($id, $parameters, $domain, $locale); + } + + /** + * Translates the given choice message by choosing a translation according to a number. + * + * @param string $id The message id + * @param int $number The number to use to find the indice of the message + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + */ + public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this['translator']->transChoice($id, $number, $parameters, $domain, $locale); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/TwigTrait.php b/vendor/silex/silex/src/Silex/Application/TwigTrait.php new file mode 100644 index 00000000..cb4127d7 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/TwigTrait.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Twig trait. + * + * @author Fabien Potencier + */ +trait TwigTrait +{ + /** + * Renders a view and returns a Response. + * + * To stream a view, pass an instance of StreamedResponse as a third argument. + * + * @param string $view The view name + * @param array $parameters An array of parameters to pass to the view + * @param Response $response A Response instance + * + * @return Response A Response instance + */ + public function render($view, array $parameters = array(), Response $response = null) + { + $twig = $this['twig']; + + if ($response instanceof StreamedResponse) { + $response->setCallback(function () use ($twig, $view, $parameters) { + $twig->display($view, $parameters); + }); + } else { + if (null === $response) { + $response = new Response(); + } + $response->setContent($twig->render($view, $parameters)); + } + + return $response; + } + + /** + * Renders a view. + * + * @param string $view The view name + * @param array $parameters An array of parameters to pass to the view + * + * @return string The rendered view + */ + public function renderView($view, array $parameters = array()) + { + return $this['twig']->render($view, $parameters); + } +} diff --git a/vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php b/vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php new file mode 100644 index 00000000..7ccdf8ac --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/UrlGeneratorTrait.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Application; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * UrlGenerator trait. + * + * @author Fabien Potencier + */ +trait UrlGeneratorTrait +{ + /** + * Generates a path from the given parameters. + * + * @param string $route The name of the route + * @param mixed $parameters An array of parameters + * + * @return string The generated path + */ + public function path($route, $parameters = array()) + { + return $this['url_generator']->generate($route, $parameters, UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * Generates an absolute URL from the given parameters. + * + * @param string $route The name of the route + * @param mixed $parameters An array of parameters + * + * @return string The generated URL + */ + public function url($route, $parameters = array()) + { + return $this['url_generator']->generate($route, $parameters, UrlGeneratorInterface::ABSOLUTE_URL); + } +} diff --git a/vendor/silex/silex/src/Silex/CallbackResolver.php b/vendor/silex/silex/src/Silex/CallbackResolver.php new file mode 100644 index 00000000..692901c2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/CallbackResolver.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Pimple\Container; + +class CallbackResolver +{ + const SERVICE_PATTERN = "/[A-Za-z0-9\._\-]+:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/"; + + private $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + /** + * Returns true if the string is a valid service method representation. + * + * @param string $name + * + * @return bool + */ + public function isValid($name) + { + return is_string($name) && (preg_match(static::SERVICE_PATTERN, $name) || isset($this->app[$name])); + } + + /** + * Returns a callable given its string representation. + * + * @param string $name + * + * @return callable + * + * @throws \InvalidArgumentException In case the method does not exist. + */ + public function convertCallback($name) + { + if (preg_match(static::SERVICE_PATTERN, $name)) { + list($service, $method) = explode(':', $name, 2); + $callback = array($this->app[$service], $method); + } else { + $service = $name; + $callback = $this->app[$name]; + } + + if (!is_callable($callback)) { + throw new \InvalidArgumentException(sprintf('Service "%s" is not callable.', $service)); + } + + return $callback; + } + + /** + * Returns a callable given its string representation if it is a valid service method. + * + * @param string $name + * + * @return string|callable A callable value or the string passed in + * + * @throws \InvalidArgumentException In case the method does not exist. + */ + public function resolveCallback($name) + { + return $this->isValid($name) ? $this->convertCallback($name) : $name; + } +} diff --git a/vendor/silex/silex/src/Silex/Controller.php b/vendor/silex/silex/src/Silex/Controller.php new file mode 100644 index 00000000..9a807559 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Controller.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 Silex; + +use Silex\Exception\ControllerFrozenException; + +/** + * A wrapper for a controller, mapped to a route. + * + * __call() forwards method-calls to Route, but returns instance of Controller + * listing Route's methods below, so that IDEs know they are valid + * + * @method Controller assert(string $variable, string $regexp) + * @method Controller value(string $variable, mixed $default) + * @method Controller convert(string $variable, mixed $callback) + * @method Controller method(string $method) + * @method Controller requireHttp() + * @method Controller requireHttps() + * @method Controller before(mixed $callback) + * @method Controller after(mixed $callback) + * @method Controller when(string $condition) + * + * @author Igor Wiedler + */ +class Controller +{ + private $route; + private $routeName; + private $isFrozen = false; + + /** + * Constructor. + * + * @param Route $route + */ + public function __construct(Route $route) + { + $this->route = $route; + } + + /** + * Gets the controller's route. + * + * @return Route + */ + public function getRoute() + { + return $this->route; + } + + /** + * Gets the controller's route name. + * + * @return string + */ + public function getRouteName() + { + return $this->routeName; + } + + /** + * Sets the controller's route. + * + * @param string $routeName + * + * @return Controller $this The current Controller instance + */ + public function bind($routeName) + { + if ($this->isFrozen) { + throw new ControllerFrozenException(sprintf('Calling %s on frozen %s instance.', __METHOD__, __CLASS__)); + } + + $this->routeName = $routeName; + + return $this; + } + + public function __call($method, $arguments) + { + if (!method_exists($this->route, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', get_class($this->route), $method)); + } + + call_user_func_array(array($this->route, $method), $arguments); + + return $this; + } + + /** + * Freezes the controller. + * + * Once the controller is frozen, you can no longer change the route name + */ + public function freeze() + { + $this->isFrozen = true; + } + + public function generateRouteName($prefix) + { + $methods = implode('_', $this->route->getMethods()).'_'; + + $routeName = $methods.$prefix.$this->route->getPath(); + $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); + $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); + + // Collapse consecutive underscores down into a single underscore. + $routeName = preg_replace('/_+/', '_', $routeName); + + return $routeName; + } +} diff --git a/vendor/silex/silex/src/Silex/ControllerCollection.php b/vendor/silex/silex/src/Silex/ControllerCollection.php new file mode 100644 index 00000000..40368964 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ControllerCollection.php @@ -0,0 +1,239 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\HttpFoundation\Request; + +/** + * Builds Silex controllers. + * + * It acts as a staging area for routes. You are able to set the route name + * until flush() is called, at which point all controllers are frozen and + * converted to a RouteCollection. + * + * __call() forwards method-calls to Route, but returns instance of ControllerCollection + * listing Route's methods below, so that IDEs know they are valid + * + * @method ControllerCollection assert(string $variable, string $regexp) + * @method ControllerCollection value(string $variable, mixed $default) + * @method ControllerCollection convert(string $variable, mixed $callback) + * @method ControllerCollection method(string $method) + * @method ControllerCollection requireHttp() + * @method ControllerCollection requireHttps() + * @method ControllerCollection before(mixed $callback) + * @method ControllerCollection after(mixed $callback) + * @method ControllerCollection when(string $condition) + * + * @author Igor Wiedler + * @author Fabien Potencier + */ +class ControllerCollection +{ + protected $controllers = array(); + protected $defaultRoute; + protected $defaultController; + protected $prefix; + protected $routesFactory; + protected $controllersFactory; + + public function __construct(Route $defaultRoute, RouteCollection $routesFactory = null, $controllersFactory = null) + { + $this->defaultRoute = $defaultRoute; + $this->routesFactory = $routesFactory; + $this->controllersFactory = $controllersFactory; + $this->defaultController = function (Request $request) { + throw new \LogicException(sprintf('The "%s" route must have code to run when it matches.', $request->attributes->get('_route'))); + }; + } + + /** + * Mounts controllers under the given route prefix. + * + * @param string $prefix The route prefix + * @param ControllerCollection|callable $controllers A ControllerCollection instance or a callable for defining routes + * + * @throws \LogicException + */ + public function mount($prefix, $controllers) + { + if (is_callable($controllers)) { + $collection = $this->controllersFactory ? call_user_func($this->controllersFactory) : new static(new Route(), new RouteCollection()); + call_user_func($controllers, $collection); + $controllers = $collection; + } elseif (!$controllers instanceof self) { + throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance or callable.'); + } + + $controllers->prefix = $prefix; + + $this->controllers[] = $controllers; + } + + /** + * Maps a pattern to a callable. + * + * You can optionally specify HTTP methods that should be matched. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function match($pattern, $to = null) + { + $route = clone $this->defaultRoute; + $route->setPath($pattern); + $this->controllers[] = $controller = new Controller($route); + $route->setDefault('_controller', null === $to ? $this->defaultController : $to); + + return $controller; + } + + /** + * Maps a GET request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function get($pattern, $to = null) + { + return $this->match($pattern, $to)->method('GET'); + } + + /** + * Maps a POST request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function post($pattern, $to = null) + { + return $this->match($pattern, $to)->method('POST'); + } + + /** + * Maps a PUT request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function put($pattern, $to = null) + { + return $this->match($pattern, $to)->method('PUT'); + } + + /** + * Maps a DELETE request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function delete($pattern, $to = null) + { + return $this->match($pattern, $to)->method('DELETE'); + } + + /** + * Maps an OPTIONS request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function options($pattern, $to = null) + { + return $this->match($pattern, $to)->method('OPTIONS'); + } + + /** + * Maps a PATCH request to a callable. + * + * @param string $pattern Matched route pattern + * @param mixed $to Callback that returns the response when matched + * + * @return Controller + */ + public function patch($pattern, $to = null) + { + return $this->match($pattern, $to)->method('PATCH'); + } + + public function __call($method, $arguments) + { + if (!method_exists($this->defaultRoute, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s::%s" does not exist.', get_class($this->defaultRoute), $method)); + } + + call_user_func_array(array($this->defaultRoute, $method), $arguments); + + foreach ($this->controllers as $controller) { + call_user_func_array(array($controller, $method), $arguments); + } + + return $this; + } + + /** + * Persists and freezes staged controllers. + * + * @return RouteCollection A RouteCollection instance + */ + public function flush() + { + if (null === $this->routesFactory) { + $routes = new RouteCollection(); + } else { + $routes = $this->routesFactory; + } + + return $this->doFlush('', $routes); + } + + private function doFlush($prefix, RouteCollection $routes) + { + if ($prefix !== '') { + $prefix = '/'.trim(trim($prefix), '/'); + } + + foreach ($this->controllers as $controller) { + if ($controller instanceof Controller) { + $controller->getRoute()->setPath($prefix.$controller->getRoute()->getPath()); + if (!$name = $controller->getRouteName()) { + $name = $base = $controller->generateRouteName(''); + $i = 0; + while ($routes->get($name)) { + $name = $base.'_'.++$i; + } + $controller->bind($name); + } + $routes->add($name, $controller->getRoute()); + $controller->freeze(); + } else { + $controller->doFlush($prefix.$controller->prefix, $routes); + } + } + + $this->controllers = array(); + + return $routes; + } +} diff --git a/vendor/silex/silex/src/Silex/ControllerResolver.php b/vendor/silex/silex/src/Silex/ControllerResolver.php new file mode 100644 index 00000000..0a95e15f --- /dev/null +++ b/vendor/silex/silex/src/Silex/ControllerResolver.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolver as BaseControllerResolver; +use Symfony\Component\HttpFoundation\Request; + +/** + * Adds Application as a valid argument for controllers. + * + * @author Fabien Potencier + * + * @deprecated This class can be dropped once Symfony 3.0 is not supported anymore. + */ +class ControllerResolver extends BaseControllerResolver +{ + protected $app; + + /** + * Constructor. + * + * @param Application $app An Application instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(Application $app, LoggerInterface $logger = null) + { + $this->app = $app; + + parent::__construct($logger); + } + + protected function doGetArguments(Request $request, $controller, array $parameters) + { + foreach ($parameters as $param) { + if ($param->getClass() && $param->getClass()->isInstance($this->app)) { + $request->attributes->set($param->getName(), $this->app); + + break; + } + } + + return parent::doGetArguments($request, $controller, $parameters); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/ConverterListener.php b/vendor/silex/silex/src/Silex/EventListener/ConverterListener.php new file mode 100644 index 00000000..2fa93c19 --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/ConverterListener.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\EventListener; + +use Silex\CallbackResolver; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Routing\RouteCollection; + +/** + * Handles converters. + * + * @author Fabien Potencier + */ +class ConverterListener implements EventSubscriberInterface +{ + protected $routes; + protected $callbackResolver; + + /** + * Constructor. + * + * @param RouteCollection $routes A RouteCollection instance + * @param CallbackResolver $callbackResolver A CallbackResolver instance + */ + public function __construct(RouteCollection $routes, CallbackResolver $callbackResolver) + { + $this->routes = $routes; + $this->callbackResolver = $callbackResolver; + } + + /** + * Handles converters. + * + * @param FilterControllerEvent $event The event to handle + */ + public function onKernelController(FilterControllerEvent $event) + { + $request = $event->getRequest(); + $route = $this->routes->get($request->attributes->get('_route')); + if ($route && $converters = $route->getOption('_converters')) { + foreach ($converters as $name => $callback) { + $callback = $this->callbackResolver->resolveCallback($callback); + + $request->attributes->set($name, call_user_func($callback, $request->attributes->get($name), $request)); + } + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::CONTROLLER => 'onKernelController', + ); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/LogListener.php b/vendor/silex/silex/src/Silex/EventListener/LogListener.php new file mode 100644 index 00000000..5f3cc904 --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/LogListener.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\EventListener; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; + +/** + * Logs request, response, and exceptions. + */ +class LogListener implements EventSubscriberInterface +{ + protected $logger; + protected $exceptionLogFilter; + + public function __construct(LoggerInterface $logger, $exceptionLogFilter = null) + { + $this->logger = $logger; + if (null === $exceptionLogFilter) { + $exceptionLogFilter = function (\Exception $e) { + if ($e instanceof HttpExceptionInterface && $e->getStatusCode() < 500) { + return LogLevel::ERROR; + } + + return LogLevel::CRITICAL; + }; + } + + $this->exceptionLogFilter = $exceptionLogFilter; + } + + /** + * Logs master requests on event KernelEvents::REQUEST. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $this->logRequest($event->getRequest()); + } + + /** + * Logs master response on event KernelEvents::RESPONSE. + * + * @param FilterResponseEvent $event + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $this->logResponse($event->getResponse()); + } + + /** + * Logs uncaught exceptions on event KernelEvents::EXCEPTION. + * + * @param GetResponseForExceptionEvent $event + */ + public function onKernelException(GetResponseForExceptionEvent $event) + { + $this->logException($event->getException()); + } + + /** + * Logs a request. + * + * @param Request $request + */ + protected function logRequest(Request $request) + { + $this->logger->log(LogLevel::DEBUG, '> '.$request->getMethod().' '.$request->getRequestUri()); + } + + /** + * Logs a response. + * + * @param Response $response + */ + protected function logResponse(Response $response) + { + $message = '< '.$response->getStatusCode(); + + if ($response instanceof RedirectResponse) { + $message .= ' '.$response->getTargetUrl(); + } + + $this->logger->log(LogLevel::DEBUG, $message); + } + + /** + * Logs an exception. + */ + protected function logException(\Exception $e) + { + $this->logger->log(call_user_func($this->exceptionLogFilter, $e), sprintf('%s: %s (uncaught exception) at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), array('exception' => $e)); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 0), + KernelEvents::RESPONSE => array('onKernelResponse', 0), + /* + * Priority -4 is used to come after those from SecurityServiceProvider (0) + * but before the error handlers added with Silex\Application::error (defaults to -8) + */ + KernelEvents::EXCEPTION => array('onKernelException', -4), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php b/vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php new file mode 100644 index 00000000..9b28ff1a --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/MiddlewareListener.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\EventListener; + +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Silex\Application; + +/** + * Manages the route middlewares. + * + * @author Fabien Potencier + */ +class MiddlewareListener implements EventSubscriberInterface +{ + protected $app; + + /** + * Constructor. + * + * @param Application $app An Application instance + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * Runs before filters. + * + * @param GetResponseEvent $event The event to handle + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $routeName = $request->attributes->get('_route'); + if (!$route = $this->app['routes']->get($routeName)) { + return; + } + + foreach ((array) $route->getOption('_before_middlewares') as $callback) { + $ret = call_user_func($this->app['callback_resolver']->resolveCallback($callback), $request, $this->app); + if ($ret instanceof Response) { + $event->setResponse($ret); + + return; + } elseif (null !== $ret) { + throw new \RuntimeException(sprintf('A before middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName)); + } + } + } + + /** + * Runs after filters. + * + * @param FilterResponseEvent $event The event to handle + */ + public function onKernelResponse(FilterResponseEvent $event) + { + $request = $event->getRequest(); + $routeName = $request->attributes->get('_route'); + if (!$route = $this->app['routes']->get($routeName)) { + return; + } + + foreach ((array) $route->getOption('_after_middlewares') as $callback) { + $response = call_user_func($this->app['callback_resolver']->resolveCallback($callback), $request, $event->getResponse(), $this->app); + if ($response instanceof Response) { + $event->setResponse($response); + } elseif (null !== $response) { + throw new \RuntimeException(sprintf('An after middleware for route "%s" returned an invalid response value. Must return null or an instance of Response.', $routeName)); + } + } + } + + public static function getSubscribedEvents() + { + return array( + // this must be executed after the late events defined with before() (and their priority is -512) + KernelEvents::REQUEST => array('onKernelRequest', -1024), + KernelEvents::RESPONSE => array('onKernelResponse', 128), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.php b/vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.php new file mode 100644 index 00000000..9fdba5fe --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/StringToResponseListener.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 Silex\EventListener; + +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; + +/** + * Converts string responses to proper Response instances. + * + * @author Fabien Potencier + */ +class StringToResponseListener implements EventSubscriberInterface +{ + /** + * Handles string responses. + * + * @param GetResponseForControllerResultEvent $event The event to handle + */ + public function onKernelView(GetResponseForControllerResultEvent $event) + { + $response = $event->getControllerResult(); + + if (!( + null === $response + || is_array($response) + || $response instanceof Response + || (is_object($response) && !method_exists($response, '__toString')) + )) { + $event->setResponse(new Response((string) $response)); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::VIEW => array('onKernelView', -10), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php b/vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php new file mode 100644 index 00000000..7f0d65f1 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Exception/ControllerFrozenException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Exception; + +/** + * Exception, is thrown when a frozen controller is modified. + * + * @author Igor Wiedler + */ +class ControllerFrozenException extends \RuntimeException +{ +} diff --git a/vendor/silex/silex/src/Silex/ExceptionHandler.php b/vendor/silex/silex/src/Silex/ExceptionHandler.php new file mode 100644 index 00000000..34eb8937 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ExceptionHandler.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 Silex; + +use Symfony\Component\Debug\ExceptionHandler as DebugExceptionHandler; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Default exception handler. + * + * @author Fabien Potencier + */ +class ExceptionHandler implements EventSubscriberInterface +{ + protected $debug; + + public function __construct($debug) + { + $this->debug = $debug; + } + + public function onSilexError(GetResponseForExceptionEvent $event) + { + $handler = new DebugExceptionHandler($this->debug); + + $exception = $event->getException(); + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + $response = Response::create($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders())->setCharset(ini_get('default_charset')); + + $event->setResponse($response); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array(KernelEvents::EXCEPTION => array('onSilexError', -255)); + } +} diff --git a/vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php b/vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php new file mode 100644 index 00000000..e0d527b0 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ExceptionListenerWrapper.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; + +/** + * Wraps exception listeners. + * + * @author Fabien Potencier + */ +class ExceptionListenerWrapper +{ + protected $app; + protected $callback; + + /** + * Constructor. + * + * @param Application $app An Application instance + * @param callable $callback + */ + public function __construct(Application $app, $callback) + { + $this->app = $app; + $this->callback = $callback; + } + + public function __invoke(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + $this->callback = $this->app['callback_resolver']->resolveCallback($this->callback); + + if (!$this->shouldRun($exception)) { + return; + } + + $code = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500; + + $response = call_user_func($this->callback, $exception, $event->getRequest(), $code); + + $this->ensureResponse($response, $event); + } + + protected function shouldRun(\Exception $exception) + { + if (is_array($this->callback)) { + $callbackReflection = new \ReflectionMethod($this->callback[0], $this->callback[1]); + } elseif (is_object($this->callback) && !$this->callback instanceof \Closure) { + $callbackReflection = new \ReflectionObject($this->callback); + $callbackReflection = $callbackReflection->getMethod('__invoke'); + } else { + $callbackReflection = new \ReflectionFunction($this->callback); + } + + if ($callbackReflection->getNumberOfParameters() > 0) { + $parameters = $callbackReflection->getParameters(); + $expectedException = $parameters[0]; + if ($expectedException->getClass() && !$expectedException->getClass()->isInstance($exception)) { + return false; + } + } + + return true; + } + + protected function ensureResponse($response, GetResponseForExceptionEvent $event) + { + if ($response instanceof Response) { + $event->setResponse($response); + } else { + $viewEvent = new GetResponseForControllerResultEvent($this->app['kernel'], $event->getRequest(), $event->getRequestType(), $response); + $this->app['dispatcher']->dispatch(KernelEvents::VIEW, $viewEvent); + + if ($viewEvent->hasResponse()) { + $event->setResponse($viewEvent->getResponse()); + } + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php new file mode 100644 index 00000000..6793f676 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/AssetServiceProvider.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Asset\Packages; +use Symfony\Component\Asset\Package; +use Symfony\Component\Asset\PathPackage; +use Symfony\Component\Asset\UrlPackage; +use Symfony\Component\Asset\Context\RequestStackContext; +use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; + +/** + * Symfony Asset component Provider. + * + * @author Fabien Potencier + */ +class AssetServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['assets.packages'] = function ($app) { + $packages = array(); + foreach ($app['assets.named_packages'] as $name => $package) { + $version = $app['assets.strategy_factory'](isset($package['version']) ? $package['version'] : '', isset($package['version_format']) ? $package['version_format'] : null); + + $packages[$name] = $app['assets.package_factory'](isset($package['base_path']) ? $package['base_path'] : '', isset($package['base_urls']) ? $package['base_urls'] : array(), $version, $name); + } + + return new Packages($app['assets.default_package'], $packages); + }; + + $app['assets.default_package'] = function ($app) { + $version = $app['assets.strategy_factory']($app['assets.version'], $app['assets.version_format']); + + return $app['assets.package_factory']($app['assets.base_path'], $app['assets.base_urls'], $version, 'default'); + }; + + $app['assets.context'] = function ($app) { + return new RequestStackContext($app['request_stack']); + }; + + $app['assets.base_path'] = ''; + $app['assets.base_urls'] = array(); + $app['assets.version'] = null; + $app['assets.version_format'] = null; + + $app['assets.named_packages'] = array(); + + // prototypes + + $app['assets.strategy_factory'] = $app->protect(function ($version, $format) use ($app) { + if (!$version) { + return new EmptyVersionStrategy(); + } + + return new StaticVersionStrategy($version, $format); + }); + + $app['assets.package_factory'] = $app->protect(function ($basePath, $baseUrls, $version, $name) use ($app) { + if ($basePath && $baseUrls) { + throw new \LogicException(sprintf('Asset package "%s" cannot have base URLs and base paths.', $name)); + } + + if (!$baseUrls) { + return new PathPackage($basePath, $version, $app['assets.context']); + } + + return new UrlPackage($baseUrls, $version, $app['assets.context']); + }); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php new file mode 100644 index 00000000..eb6e882d --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/CsrfServiceProvider.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; + +/** + * Symfony CSRF Security component Provider. + * + * @author Fabien Potencier + */ +class CsrfServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['csrf.token_manager'] = function ($app) { + return new CsrfTokenManager($app['csrf.token_generator'], $app['csrf.token_storage']); + }; + + $app['csrf.token_storage'] = function ($app) { + if (isset($app['session'])) { + return new SessionTokenStorage($app['session'], $app['csrf.session_namespace']); + } + + return new NativeSessionTokenStorage($app['csrf.session_namespace']); + }; + + $app['csrf.token_generator'] = function ($app) { + return new UriSafeTokenGenerator(); + }; + + $app['csrf.session_namespace'] = '_csrf'; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php new file mode 100644 index 00000000..9c71d5b7 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Configuration; +use Doctrine\Common\EventManager; +use Symfony\Bridge\Doctrine\Logger\DbalLogger; + +/** + * Doctrine DBAL Provider. + * + * @author Fabien Potencier + */ +class DoctrineServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['db.default_options'] = array( + 'driver' => 'pdo_mysql', + 'dbname' => null, + 'host' => 'localhost', + 'user' => 'root', + 'password' => null, + ); + + $app['dbs.options.initializer'] = $app->protect(function () use ($app) { + static $initialized = false; + + if ($initialized) { + return; + } + + $initialized = true; + + if (!isset($app['dbs.options'])) { + $app['dbs.options'] = array('default' => isset($app['db.options']) ? $app['db.options'] : array()); + } + + $tmp = $app['dbs.options']; + foreach ($tmp as $name => &$options) { + $options = array_replace($app['db.default_options'], $options); + + if (!isset($app['dbs.default'])) { + $app['dbs.default'] = $name; + } + } + $app['dbs.options'] = $tmp; + }); + + $app['dbs'] = function ($app) { + $app['dbs.options.initializer'](); + + $dbs = new Container(); + foreach ($app['dbs.options'] as $name => $options) { + if ($app['dbs.default'] === $name) { + // we use shortcuts here in case the default has been overridden + $config = $app['db.config']; + $manager = $app['db.event_manager']; + } else { + $config = $app['dbs.config'][$name]; + $manager = $app['dbs.event_manager'][$name]; + } + + $dbs[$name] = function ($dbs) use ($options, $config, $manager) { + return DriverManager::getConnection($options, $config, $manager); + }; + } + + return $dbs; + }; + + $app['dbs.config'] = function ($app) { + $app['dbs.options.initializer'](); + + $configs = new Container(); + $addLogger = isset($app['logger']) && null !== $app['logger'] && class_exists('Symfony\Bridge\Doctrine\Logger\DbalLogger'); + foreach ($app['dbs.options'] as $name => $options) { + $configs[$name] = new Configuration(); + if ($addLogger) { + $configs[$name]->setSQLLogger(new DbalLogger($app['logger'], isset($app['stopwatch']) ? $app['stopwatch'] : null)); + } + } + + return $configs; + }; + + $app['dbs.event_manager'] = function ($app) { + $app['dbs.options.initializer'](); + + $managers = new Container(); + foreach ($app['dbs.options'] as $name => $options) { + $managers[$name] = new EventManager(); + } + + return $managers; + }; + + // shortcuts for the "first" DB + $app['db'] = function ($app) { + $dbs = $app['dbs']; + + return $dbs[$app['dbs.default']]; + }; + + $app['db.config'] = function ($app) { + $dbs = $app['dbs.config']; + + return $dbs[$app['dbs.default']]; + }; + + $app['db.event_manager'] = function ($app) { + $dbs = $app['dbs.event_manager']; + + return $dbs[$app['dbs.default']]; + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php new file mode 100644 index 00000000..1c6f2028 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ExceptionHandlerServiceProvider.php @@ -0,0 +1,32 @@ +addSubscriber($app['exception_handler']); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php b/vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.php new file mode 100644 index 00000000..12efbdf2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Form/SilexFormExtension.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 Silex\Provider\Form; + +use Pimple\Container; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\FormExtensionInterface; +use Symfony\Component\Form\FormTypeGuesserChain; + +class SilexFormExtension implements FormExtensionInterface +{ + private $app; + private $types; + private $typeExtensions; + private $guessers; + private $guesserLoaded = false; + private $guesser; + + public function __construct(Container $app, array $types, array $typeExtensions, array $guessers) + { + $this->app = $app; + $this->setTypes($types); + $this->setTypeExtensions($typeExtensions); + $this->setGuessers($guessers); + } + + public function getType($name) + { + if (!isset($this->types[$name])) { + throw new InvalidArgumentException(sprintf('The type "%s" is not the name of a registered form type.', $name)); + } + if (!is_object($this->types[$name])) { + $this->types[$name] = $this->app[$this->types[$name]]; + } + + return $this->types[$name]; + } + + public function hasType($name) + { + return isset($this->types[$name]); + } + + public function getTypeExtensions($name) + { + return isset($this->typeExtensions[$name]) ? $this->typeExtensions[$name] : []; + } + + public function hasTypeExtensions($name) + { + return isset($this->typeExtensions[$name]); + } + + public function getTypeGuesser() + { + if (!$this->guesserLoaded) { + $this->guesserLoaded = true; + + if ($this->guessers) { + $guessers = []; + foreach ($this->guessers as $guesser) { + if (!is_object($guesser)) { + $guesser = $this->app[$guesser]; + } + $guessers[] = $guesser; + } + $this->guesser = new FormTypeGuesserChain($guessers); + } + } + + return $this->guesser; + } + + private function setTypes(array $types) + { + $this->types = []; + foreach ($types as $type) { + if (!is_object($type)) { + if (!isset($this->app[$type])) { + throw new InvalidArgumentException(sprintf('Invalid form type. The silex service "%s" does not exist.', $type)); + } + $this->types[$type] = $type; + } else { + $this->types[get_class($type)] = $type; + } + } + } + + private function setTypeExtensions(array $typeExtensions) + { + $this->typeExtensions = []; + foreach ($typeExtensions as $extension) { + if (!is_object($extension)) { + if (!isset($this->app[$extension])) { + throw new InvalidArgumentException(sprintf('Invalid form type extension. The silex service "%s" does not exist.', $extension)); + } + $extension = $this->app[$extension]; + } + $this->typeExtensions[$extension->getExtendedType()][] = $extension; + } + } + + private function setGuessers(array $guessers) + { + $this->guessers = []; + foreach ($guessers as $guesser) { + if (!is_object($guesser) && !isset($this->app[$guesser])) { + throw new InvalidArgumentException(sprintf('Invalid form type guesser. The silex service "%s" does not exist.', $guesser)); + } + $this->guessers[] = $guesser; + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php new file mode 100644 index 00000000..00841d0b --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Form\Extension\Csrf\CsrfExtension; +use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension; +use Symfony\Component\Form\Extension\Validator\ValidatorExtension as FormValidatorExtension; +use Symfony\Component\Form\FormFactory; +use Symfony\Component\Form\FormRegistry; +use Symfony\Component\Form\ResolvedFormTypeFactory; + +/** + * Symfony Form component Provider. + * + * @author Fabien Potencier + */ +class FormServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + if (!class_exists('Locale')) { + throw new \RuntimeException('You must either install the PHP intl extension or the Symfony Intl Component to use the Form extension.'); + } + + $app['form.types'] = function ($app) { + return array(); + }; + + $app['form.type.extensions'] = function ($app) { + return array(); + }; + + $app['form.type.guessers'] = function ($app) { + return array(); + }; + + $app['form.extension.csrf'] = function ($app) { + if (isset($app['translator'])) { + return new CsrfExtension($app['csrf.token_manager'], $app['translator']); + } + + return new CsrfExtension($app['csrf.token_manager']); + }; + + $app['form.extension.silex'] = function ($app) { + return new Form\SilexFormExtension($app, $app['form.types'], $app['form.type.extensions'], $app['form.type.guessers']); + }; + + $app['form.extensions'] = function ($app) { + $extensions = array( + new HttpFoundationExtension(), + ); + + if (isset($app['csrf.token_manager'])) { + $extensions[] = $app['form.extension.csrf']; + } + + if (isset($app['validator'])) { + $extensions[] = new FormValidatorExtension($app['validator']); + } + $extensions[] = $app['form.extension.silex']; + + return $extensions; + }; + + $app['form.factory'] = function ($app) { + return new FormFactory($app['form.registry'], $app['form.resolved_type_factory']); + }; + + $app['form.registry'] = function ($app) { + return new FormRegistry($app['form.extensions'], $app['form.resolved_type_factory']); + }; + + $app['form.resolved_type_factory'] = function ($app) { + return new ResolvedFormTypeFactory(); + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php b/vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php new file mode 100644 index 00000000..b0ebb5cc --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpCache/HttpCache.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\HttpCache; + +use Symfony\Component\HttpKernel\HttpCache\HttpCache as BaseHttpCache; +use Symfony\Component\HttpFoundation\Request; + +/** + * HTTP Cache extension to allow using the run() shortcut. + * + * @author Fabien Potencier + */ +class HttpCache extends BaseHttpCache +{ + /** + * Handles the Request and delivers the Response. + * + * @param Request $request The Request object + */ + public function run(Request $request = null) + { + if (null === $request) { + $request = Request::createFromGlobals(); + } + + $response = $this->handle($request); + $response->send(); + $this->terminate($request, $response); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php new file mode 100644 index 00000000..8b3f37ee --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Provider\HttpCache\HttpCache; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; + +/** + * Symfony HttpKernel component Provider for HTTP cache. + * + * @author Fabien Potencier + */ +class HttpCacheServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['http_cache'] = function ($app) { + $app['http_cache.options'] = array_replace( + array( + 'debug' => $app['debug'], + ), $app['http_cache.options'] + ); + + return new HttpCache($app, $app['http_cache.store'], $app['http_cache.esi'], $app['http_cache.options']); + }; + + $app['http_cache.esi'] = function ($app) { + return new Esi(); + }; + + $app['http_cache.store'] = function ($app) { + return new Store($app['http_cache.cache_dir']); + }; + + $app['http_cache.esi_listener'] = function ($app) { + return new SurrogateListener($app['http_cache.esi']); + }; + + $app['http_cache.options'] = array(); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['http_cache.esi_listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php new file mode 100644 index 00000000..9a641f40 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * HttpKernel Fragment integration for Silex. + * + * @author Fabien Potencier + */ +class HttpFragmentServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['fragment.handler'] = function ($app) { + return new FragmentHandler($app['request_stack'], $app['fragment.renderers'], $app['debug']); + }; + + $app['fragment.renderer.inline'] = function ($app) { + $renderer = new InlineFragmentRenderer($app['kernel'], $app['dispatcher']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }; + + $app['fragment.renderer.hinclude'] = function ($app) { + $renderer = new HIncludeFragmentRenderer(null, $app['uri_signer'], $app['fragment.renderer.hinclude.global_template'], $app['charset']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }; + + $app['fragment.renderer.esi'] = function ($app) { + $renderer = new EsiFragmentRenderer($app['http_cache.esi'], $app['fragment.renderer.inline']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }; + + $app['fragment.listener'] = function ($app) { + return new FragmentListener($app['uri_signer'], $app['fragment.path']); + }; + + $app['uri_signer'] = function ($app) { + return new UriSigner($app['uri_signer.secret']); + }; + + $app['uri_signer.secret'] = md5(__DIR__); + $app['fragment.path'] = '/_fragment'; + $app['fragment.renderer.hinclude.global_template'] = null; + $app['fragment.renderers'] = function ($app) { + $renderers = array($app['fragment.renderer.inline'], $app['fragment.renderer.hinclude']); + + if (isset($app['http_cache.esi'])) { + $renderers[] = $app['fragment.renderer.esi']; + } + + return $renderers; + }; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['fragment.listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php new file mode 100644 index 00000000..86f155fe --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpKernelServiceProvider.php @@ -0,0 +1,101 @@ += 30100) { + return new SfControllerResolver($app['logger']); + } + + return new ControllerResolver($app, $app['logger']); + }; + + if (Kernel::VERSION_ID >= 30100) { + $app['argument_metadata_factory'] = function ($app) { + return new ArgumentMetadataFactory(); + }; + $app['argument_value_resolvers'] = function ($app) { + if (Kernel::VERSION_ID < 30200) { + return array( + new AppArgumentValueResolver($app), + new RequestAttributeValueResolver(), + new RequestValueResolver(), + new DefaultValueResolver(), + new VariadicValueResolver(), + ); + } + + return array_merge(array(new AppArgumentValueResolver($app)), ArgumentResolver::getDefaultArgumentValueResolvers()); + }; + } + + $app['argument_resolver'] = function ($app) { + if (Kernel::VERSION_ID >= 30100) { + return new ArgumentResolver($app['argument_metadata_factory'], $app['argument_value_resolvers']); + } + }; + + $app['kernel'] = function ($app) { + return new HttpKernel($app['dispatcher'], $app['resolver'], $app['request_stack'], $app['argument_resolver']); + }; + + $app['request_stack'] = function () { + return new RequestStack(); + }; + + $app['dispatcher'] = function () { + return new EventDispatcher(); + }; + + $app['callback_resolver'] = function ($app) { + return new CallbackResolver($app); + }; + } + + /** + * {@inheritdoc} + */ + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber(new ResponseListener($app['charset'])); + $dispatcher->addSubscriber(new MiddlewareListener($app)); + $dispatcher->addSubscriber(new ConverterListener($app['routes'], $app['callback_resolver'])); + $dispatcher->addSubscriber(new StringToResponseListener()); + + if (class_exists(HttpHeaderSerializer::class)) { + $dispatcher->addSubscriber(new AddLinkHeaderListener()); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/LICENSE b/vendor/silex/silex/src/Silex/Provider/LICENSE new file mode 100644 index 00000000..bc6ad049 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2015 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/silex/silex/src/Silex/Provider/Locale/LocaleListener.php b/vendor/silex/silex/src/Silex/Provider/Locale/LocaleListener.php new file mode 100644 index 00000000..d5002640 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Locale/LocaleListener.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 Silex\Provider\Locale; + +use Pimple\Container; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Initializes the locale based on the current request. + * + * @author Fabien Potencier + * @author Jérôme Tamarelle + */ +class LocaleListener implements EventSubscriberInterface +{ + private $app; + private $defaultLocale; + private $requestStack; + private $requestContext; + + public function __construct(Container $app, $defaultLocale = 'en', RequestStack $requestStack, RequestContext $requestContext = null) + { + $this->app = $app; + $this->defaultLocale = $defaultLocale; + $this->requestStack = $requestStack; + $this->requestContext = $requestContext; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->setDefaultLocale($this->defaultLocale); + + $this->setLocale($request); + $this->setRouterContext($request); + + $this->app['locale'] = $request->getLocale(); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null !== $parentRequest = $this->requestStack->getParentRequest()) { + $this->setRouterContext($parentRequest); + } + } + + private function setLocale(Request $request) + { + if ($locale = $request->attributes->get('_locale')) { + $request->setLocale($locale); + } + } + + private function setRouterContext(Request $request) + { + if (null !== $this->requestContext) { + $this->requestContext->setParameter('_locale', $request->getLocale()); + } + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Router to have access to the _locale + KernelEvents::REQUEST => array(array('onKernelRequest', 16)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php new file mode 100644 index 00000000..ddea81bf --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/LocaleServiceProvider.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Provider\Locale\LocaleListener; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Locale Provider. + * + * @author Fabien Potencier + */ +class LocaleServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['locale.listener'] = function ($app) { + return new LocaleListener($app, $app['locale'], $app['request_stack'], isset($app['request_context']) ? $app['request_context'] : null); + }; + + $app['locale'] = 'en'; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['locale.listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php new file mode 100644 index 00000000..f8cba4e2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Logger; +use Monolog\Handler; +use Monolog\ErrorHandler; +use Silex\Application; +use Silex\Api\BootableProviderInterface; +use Symfony\Bridge\Monolog\Handler\DebugHandler; +use Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy; +use Symfony\Bridge\Monolog\Processor\DebugProcessor; +use Silex\EventListener\LogListener; + +/** + * Monolog Provider. + * + * @author Fabien Potencier + */ +class MonologServiceProvider implements ServiceProviderInterface, BootableProviderInterface +{ + public function register(Container $app) + { + $app['logger'] = function () use ($app) { + return $app['monolog']; + }; + + if ($bridge = class_exists('Symfony\Bridge\Monolog\Logger')) { + $app['monolog.handler.debug'] = function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + return new DebugHandler($level); + }; + + if (isset($app['request_stack'])) { + $app['monolog.not_found_activation_strategy'] = function () use ($app) { + return new NotFoundActivationStrategy($app['request_stack'], array('^/'), $app['monolog.level']); + }; + } + } + + $app['monolog.logger.class'] = $bridge ? 'Symfony\Bridge\Monolog\Logger' : 'Monolog\Logger'; + + $app['monolog'] = function ($app) use ($bridge) { + $log = new $app['monolog.logger.class']($app['monolog.name']); + + $handler = new Handler\GroupHandler($app['monolog.handlers']); + if (isset($app['monolog.not_found_activation_strategy'])) { + $handler = new Handler\FingersCrossedHandler($handler, $app['monolog.not_found_activation_strategy']); + } + + $log->pushHandler($handler); + + if ($app['debug'] && $bridge) { + if (class_exists(DebugProcessor::class)) { + $log->pushProcessor(new DebugProcessor()); + } else { + $log->pushHandler($app['monolog.handler.debug']); + } + } + + return $log; + }; + + $app['monolog.formatter'] = function () { + return new LineFormatter(); + }; + + $app['monolog.handler'] = $defaultHandler = function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + $handler = new Handler\StreamHandler($app['monolog.logfile'], $level, $app['monolog.bubble'], $app['monolog.permission']); + $handler->setFormatter($app['monolog.formatter']); + + return $handler; + }; + + $app['monolog.handlers'] = function () use ($app, $defaultHandler) { + $handlers = array(); + + // enables the default handler if a logfile was set or the monolog.handler service was redefined + if ($app['monolog.logfile'] || $defaultHandler !== $app->raw('monolog.handler')) { + $handlers[] = $app['monolog.handler']; + } + + return $handlers; + }; + + $app['monolog.level'] = function () { + return Logger::DEBUG; + }; + + $app['monolog.listener'] = function () use ($app) { + return new LogListener($app['logger'], $app['monolog.exception.logger_filter']); + }; + + $app['monolog.name'] = 'app'; + $app['monolog.bubble'] = true; + $app['monolog.permission'] = null; + $app['monolog.exception.logger_filter'] = null; + $app['monolog.logfile'] = null; + $app['monolog.use_error_handler'] = function ($app) { + return !$app['debug']; + }; + } + + public function boot(Application $app) + { + if ($app['monolog.use_error_handler']) { + ErrorHandler::register($app['monolog']); + } + + if (isset($app['monolog.listener'])) { + $app['dispatcher']->addSubscriber($app['monolog.listener']); + } + } + + public static function translateLevel($name) + { + // level is already translated to logger constant, return as-is + if (is_int($name)) { + return $name; + } + + $levels = Logger::getLevels(); + $upper = strtoupper($name); + + if (!isset($levels[$upper])) { + throw new \InvalidArgumentException("Provided logging level '$name' does not exist. Must be a valid monolog logging level."); + } + + return $levels[$upper]; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php new file mode 100644 index 00000000..766631c5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Security\Core\Authentication\Provider\RememberMeAuthenticationProvider; +use Symfony\Component\Security\Http\Firewall\RememberMeListener; +use Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices; +use Symfony\Component\Security\Http\RememberMe\ResponseListener; + +/** + * Remember-me authentication for the SecurityServiceProvider. + * + * @author Jérôme Tamarelle + */ +class RememberMeServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['security.remember_me.response_listener'] = function ($app) { + if (!isset($app['security.token_storage'])) { + throw new \LogicException('You must register the SecurityServiceProvider to use the RememberMeServiceProvider'); + } + + return new ResponseListener(); + }; + + $app['security.authentication_listener.factory.remember_me'] = $app->protect(function ($name, $options) use ($app) { + if (empty($options['key'])) { + $options['key'] = $name; + } + + if (!isset($app['security.remember_me.service.'.$name])) { + $app['security.remember_me.service.'.$name] = $app['security.remember_me.service._proto']($name, $options); + } + + if (!isset($app['security.authentication_listener.'.$name.'.remember_me'])) { + $app['security.authentication_listener.'.$name.'.remember_me'] = $app['security.authentication_listener.remember_me._proto']($name, $options); + } + + if (!isset($app['security.authentication_provider.'.$name.'.remember_me'])) { + $app['security.authentication_provider.'.$name.'.remember_me'] = $app['security.authentication_provider.remember_me._proto']($name, $options); + } + + return array( + 'security.authentication_provider.'.$name.'.remember_me', + 'security.authentication_listener.'.$name.'.remember_me', + null, // entry point + 'remember_me', + ); + }); + + $app['security.remember_me.service._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return function () use ($providerKey, $options, $app) { + $options = array_replace(array( + 'name' => 'REMEMBERME', + 'lifetime' => 31536000, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'always_remember_me' => false, + 'remember_me_parameter' => '_remember_me', + ), $options); + + return new TokenBasedRememberMeServices(array($app['security.user_provider.'.$providerKey]), $options['key'], $providerKey, $options, $app['logger']); + }; + }); + + $app['security.authentication_listener.remember_me._proto'] = $app->protect(function ($providerKey) use ($app) { + return function () use ($app, $providerKey) { + $listener = new RememberMeListener( + $app['security.token_storage'], + $app['security.remember_me.service.'.$providerKey], + $app['security.authentication_manager'], + $app['logger'], + $app['dispatcher'] + ); + + return $listener; + }; + }); + + $app['security.authentication_provider.remember_me._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name, $options) { + return new RememberMeAuthenticationProvider($app['security.user_checker'], $options['key'], $name); + }; + }); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['security.remember_me.response_listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php b/vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php new file mode 100644 index 00000000..6837c79a --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Routing/LazyRequestMatcher.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Routing; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * Implements a lazy UrlMatcher. + * + * @author Igor Wiedler + * @author Jérôme Tamarelle + */ +class LazyRequestMatcher implements RequestMatcherInterface +{ + private $factory; + + public function __construct(\Closure $factory) + { + $this->factory = $factory; + } + + /** + * Returns the corresponding RequestMatcherInterface instance. + * + * @return UrlMatcherInterface + */ + public function getRequestMatcher() + { + $matcher = call_user_func($this->factory); + if (!$matcher instanceof RequestMatcherInterface) { + throw new \LogicException("Factory supplied to LazyRequestMatcher must return implementation of Symfony\Component\Routing\RequestMatcherInterface."); + } + + return $matcher; + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + return $this->getRequestMatcher()->matchRequest($request); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php b/vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php new file mode 100644 index 00000000..d2fa39e8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Routing/RedirectableUrlMatcher.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Routing; + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcher as BaseRedirectableUrlMatcher; + +/** + * Implements the RedirectableUrlMatcherInterface for Silex. + * + * @author Fabien Potencier + */ +class RedirectableUrlMatcher extends BaseRedirectableUrlMatcher +{ + /** + * {@inheritdoc} + */ + public function redirect($path, $route, $scheme = null) + { + $url = $this->context->getBaseUrl().$path; + $query = $this->context->getQueryString() ?: ''; + + if ($query !== '') { + $url .= '?'.$query; + } + + if ($this->context->getHost()) { + if ($scheme) { + $port = ''; + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $url = $scheme.'://'.$this->context->getHost().$port.$url; + } + } + + return array( + '_controller' => function ($url) { return new RedirectResponse($url, 301); }, + 'url' => $url, + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php new file mode 100644 index 00000000..d040ba0d --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/RoutingServiceProvider.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\ControllerCollection; +use Silex\Api\EventListenerProviderInterface; +use Silex\Provider\Routing\RedirectableUrlMatcher; +use Silex\Provider\Routing\LazyRequestMatcher; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Symfony Routing component Provider. + * + * @author Fabien Potencier + */ +class RoutingServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['route_class'] = 'Silex\\Route'; + + $app['route_factory'] = $app->factory(function ($app) { + return new $app['route_class'](); + }); + + $app['routes_factory'] = $app->factory(function () { + return new RouteCollection(); + }); + + $app['routes'] = function ($app) { + return $app['routes_factory']; + }; + $app['url_generator'] = function ($app) { + return new UrlGenerator($app['routes'], $app['request_context']); + }; + + $app['request_matcher'] = function ($app) { + return new RedirectableUrlMatcher($app['routes'], $app['request_context']); + }; + + $app['request_context'] = function ($app) { + $context = new RequestContext(); + + $context->setHttpPort(isset($app['request.http_port']) ? $app['request.http_port'] : 80); + $context->setHttpsPort(isset($app['request.https_port']) ? $app['request.https_port'] : 443); + + return $context; + }; + + $app['controllers'] = function ($app) { + return $app['controllers_factory']; + }; + + $controllers_factory = function () use ($app, &$controllers_factory) { + return new ControllerCollection($app['route_factory'], $app['routes_factory'], $controllers_factory); + }; + $app['controllers_factory'] = $app->factory($controllers_factory); + + $app['routing.listener'] = function ($app) { + $urlMatcher = new LazyRequestMatcher(function () use ($app) { + return $app['request_matcher']; + }); + + return new RouterListener($urlMatcher, $app['request_stack'], $app['request_context'], $app['logger']); + }; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['routing.listener']); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php new file mode 100644 index 00000000..ebc5bea6 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php @@ -0,0 +1,696 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Application; +use Silex\Api\BootableProviderInterface; +use Silex\Api\ControllerProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\User\UserChecker; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; +use Symfony\Component\Security\Core\Encoder\EncoderFactory; +use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; +use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder; +use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder; +use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; +use Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider; +use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; +use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; +use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; +use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; +use Symfony\Component\Security\Core\Role\RoleHierarchy; +use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator; +use Symfony\Component\Security\Http\Firewall; +use Symfony\Component\Security\Http\FirewallMap; +use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener; +use Symfony\Component\Security\Http\Firewall\AccessListener; +use Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener; +use Symfony\Component\Security\Http\Firewall\LogoutListener; +use Symfony\Component\Security\Http\Firewall\SwitchUserListener; +use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener; +use Symfony\Component\Security\Http\Firewall\ContextListener; +use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\ChannelListener; +use Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint; +use Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint; +use Symfony\Component\Security\Http\EntryPoint\RetryAuthenticationEntryPoint; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; +use Symfony\Component\Security\Http\Logout\SessionLogoutHandler; +use Symfony\Component\Security\Http\Logout\DefaultLogoutSuccessHandler; +use Symfony\Component\Security\Http\AccessMap; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; +use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener; +use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider; + +/** + * Symfony Security component Provider. + * + * @author Fabien Potencier + */ +class SecurityServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface, ControllerProviderInterface, BootableProviderInterface +{ + protected $fakeRoutes; + + public function register(Container $app) + { + // used to register routes for login_check and logout + $this->fakeRoutes = array(); + + $that = $this; + + $app['security.role_hierarchy'] = array(); + $app['security.access_rules'] = array(); + $app['security.hide_user_not_found'] = true; + $app['security.encoder.bcrypt.cost'] = 13; + + $app['security.authorization_checker'] = function ($app) { + return new AuthorizationChecker($app['security.token_storage'], $app['security.authentication_manager'], $app['security.access_manager']); + }; + + $app['security.token_storage'] = function ($app) { + return new TokenStorage(); + }; + + $app['user'] = $app->factory(function ($app) { + if (null === $token = $app['security.token_storage']->getToken()) { + return; + } + + if (!is_object($user = $token->getUser())) { + return; + } + + return $user; + }); + + $app['security.authentication_manager'] = function ($app) { + $manager = new AuthenticationProviderManager($app['security.authentication_providers']); + $manager->setEventDispatcher($app['dispatcher']); + + return $manager; + }; + + // by default, all users use the digest encoder + $app['security.encoder_factory'] = function ($app) { + return new EncoderFactory(array( + 'Symfony\Component\Security\Core\User\UserInterface' => $app['security.default_encoder'], + )); + }; + + // by default, all users use the BCrypt encoder + $app['security.default_encoder'] = function ($app) { + return $app['security.encoder.bcrypt']; + }; + + $app['security.encoder.digest'] = function ($app) { + return new MessageDigestPasswordEncoder(); + }; + + $app['security.encoder.bcrypt'] = function ($app) { + return new BCryptPasswordEncoder($app['security.encoder.bcrypt.cost']); + }; + + $app['security.encoder.pbkdf2'] = function ($app) { + return new Pbkdf2PasswordEncoder(); + }; + + $app['security.user_checker'] = function ($app) { + return new UserChecker(); + }; + + $app['security.access_manager'] = function ($app) { + return new AccessDecisionManager($app['security.voters']); + }; + + $app['security.voters'] = function ($app) { + return array( + new RoleHierarchyVoter(new RoleHierarchy($app['security.role_hierarchy'])), + new AuthenticatedVoter($app['security.trust_resolver']), + ); + }; + + $app['security.firewall'] = function ($app) { + return new Firewall($app['security.firewall_map'], $app['dispatcher']); + }; + + $app['security.channel_listener'] = function ($app) { + return new ChannelListener( + $app['security.access_map'], + new RetryAuthenticationEntryPoint( + isset($app['request.http_port']) ? $app['request.http_port'] : 80, + isset($app['request.https_port']) ? $app['request.https_port'] : 443 + ), + $app['logger'] + ); + }; + + // generate the build-in authentication factories + foreach (array('logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous') as $type) { + $entryPoint = null; + if ('http' === $type) { + $entryPoint = 'http'; + } elseif ('form' === $type) { + $entryPoint = 'form'; + } elseif ('guard' === $type) { + $entryPoint = 'guard'; + } + + $app['security.authentication_listener.factory.'.$type] = $app->protect(function ($name, $options) use ($type, $app, $entryPoint) { + if ($entryPoint && !isset($app['security.entry_point.'.$name.'.'.$entryPoint])) { + $app['security.entry_point.'.$name.'.'.$entryPoint] = $app['security.entry_point.'.$entryPoint.'._proto']($name, $options); + } + + if (!isset($app['security.authentication_listener.'.$name.'.'.$type])) { + $app['security.authentication_listener.'.$name.'.'.$type] = $app['security.authentication_listener.'.$type.'._proto']($name, $options); + } + + $provider = 'dao'; + if ('anonymous' === $type) { + $provider = 'anonymous'; + } elseif ('guard' === $type) { + $provider = 'guard'; + } + if (!isset($app['security.authentication_provider.'.$name.'.'.$provider])) { + $app['security.authentication_provider.'.$name.'.'.$provider] = $app['security.authentication_provider.'.$provider.'._proto']($name, $options); + } + + return array( + 'security.authentication_provider.'.$name.'.'.$provider, + 'security.authentication_listener.'.$name.'.'.$type, + $entryPoint ? 'security.entry_point.'.$name.'.'.$entryPoint : null, + $type, + ); + }); + } + + $app['security.firewall_map'] = function ($app) { + $positions = array('logout', 'pre_auth', 'guard', 'form', 'http', 'remember_me', 'anonymous'); + $providers = array(); + $configs = array(); + foreach ($app['security.firewalls'] as $name => $firewall) { + $entryPoint = null; + $pattern = isset($firewall['pattern']) ? $firewall['pattern'] : null; + $users = isset($firewall['users']) ? $firewall['users'] : array(); + $security = isset($firewall['security']) ? (bool) $firewall['security'] : true; + $stateless = isset($firewall['stateless']) ? (bool) $firewall['stateless'] : false; + $context = isset($firewall['context']) ? $firewall['context'] : $name; + $hosts = isset($firewall['hosts']) ? $firewall['hosts'] : null; + $methods = isset($firewall['methods']) ? $firewall['methods'] : null; + unset($firewall['pattern'], $firewall['users'], $firewall['security'], $firewall['stateless'], $firewall['context'], $firewall['methods'], $firewall['hosts']); + $protected = false === $security ? false : count($firewall); + $listeners = array('security.channel_listener'); + + if ($protected) { + if (!isset($app['security.context_listener.'.$name])) { + if (!isset($app['security.user_provider.'.$name])) { + $app['security.user_provider.'.$name] = is_array($users) ? $app['security.user_provider.inmemory._proto']($users) : $users; + } + + $app['security.context_listener.'.$name] = $app['security.context_listener._proto']($name, array($app['security.user_provider.'.$name])); + } + + if (false === $stateless) { + $listeners[] = 'security.context_listener.'.$context; + } + + $factories = array(); + foreach ($positions as $position) { + $factories[$position] = array(); + } + + foreach ($firewall as $type => $options) { + if ('switch_user' === $type) { + continue; + } + + // normalize options + if (!is_array($options)) { + if (!$options) { + continue; + } + + $options = array(); + } + + if (!isset($app['security.authentication_listener.factory.'.$type])) { + throw new \LogicException(sprintf('The "%s" authentication entry is not registered.', $type)); + } + + $options['stateless'] = $stateless; + + list($providerId, $listenerId, $entryPointId, $position) = $app['security.authentication_listener.factory.'.$type]($name, $options); + + if (null !== $entryPointId) { + $entryPoint = $entryPointId; + } + + $factories[$position][] = $listenerId; + $providers[] = $providerId; + } + + foreach ($positions as $position) { + foreach ($factories[$position] as $listener) { + $listeners[] = $listener; + } + } + + $listeners[] = 'security.access_listener'; + + if (isset($firewall['switch_user'])) { + $app['security.switch_user.'.$name] = $app['security.authentication_listener.switch_user._proto']($name, $firewall['switch_user']); + + $listeners[] = 'security.switch_user.'.$name; + } + + if (!isset($app['security.exception_listener.'.$name])) { + if (null == $entryPoint) { + $app[$entryPoint = 'security.entry_point.'.$name.'.form'] = $app['security.entry_point.form._proto']($name, array()); + } + $accessDeniedHandler = null; + if (isset($app['security.access_denied_handler.'.$name])) { + $accessDeniedHandler = $app['security.access_denied_handler.'.$name]; + } + $app['security.exception_listener.'.$name] = $app['security.exception_listener._proto']($entryPoint, $name, $accessDeniedHandler); + } + } + + $configs[$name] = array( + 'pattern' => $pattern, + 'listeners' => $listeners, + 'protected' => $protected, + 'methods' => $methods, + 'hosts' => $hosts, + ); + } + + $app['security.authentication_providers'] = array_map(function ($provider) use ($app) { + return $app[$provider]; + }, array_unique($providers)); + + $map = new FirewallMap(); + foreach ($configs as $name => $config) { + if (is_string($config['pattern'])) { + $requestMatcher = new RequestMatcher($config['pattern'], $config['hosts'], $config['methods']); + } else { + $requestMatcher = $config['pattern']; + } + + $map->add( + $requestMatcher, + array_map(function ($listenerId) use ($app, $name) { + $listener = $app[$listenerId]; + + if (isset($app['security.remember_me.service.'.$name])) { + if ($listener instanceof AbstractAuthenticationListener || $listener instanceof GuardAuthenticationListener) { + $listener->setRememberMeServices($app['security.remember_me.service.'.$name]); + } + if ($listener instanceof LogoutListener) { + $listener->addHandler($app['security.remember_me.service.'.$name]); + } + } + + return $listener; + }, $config['listeners']), + $config['protected'] ? $app['security.exception_listener.'.$name] : null + ); + } + + return $map; + }; + + $app['security.access_listener'] = function ($app) { + return new AccessListener( + $app['security.token_storage'], + $app['security.access_manager'], + $app['security.access_map'], + $app['security.authentication_manager'], + $app['logger'] + ); + }; + + $app['security.access_map'] = function ($app) { + $map = new AccessMap(); + + foreach ($app['security.access_rules'] as $rule) { + if (is_string($rule[0])) { + $rule[0] = new RequestMatcher($rule[0]); + } elseif (is_array($rule[0])) { + $rule[0] += array( + 'path' => null, + 'host' => null, + 'methods' => null, + 'ips' => null, + 'attributes' => array(), + 'schemes' => null, + ); + $rule[0] = new RequestMatcher($rule[0]['path'], $rule[0]['host'], $rule[0]['methods'], $rule[0]['ips'], $rule[0]['attributes'], $rule[0]['schemes']); + } + $map->add($rule[0], (array) $rule[1], isset($rule[2]) ? $rule[2] : null); + } + + return $map; + }; + + $app['security.trust_resolver'] = function ($app) { + return new AuthenticationTrustResolver('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken', 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken'); + }; + + $app['security.session_strategy'] = function ($app) { + return new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE); + }; + + $app['security.http_utils'] = function ($app) { + return new HttpUtils($app['url_generator'], $app['request_matcher']); + }; + + $app['security.last_error'] = $app->protect(function (Request $request) { + if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { + return $request->attributes->get(Security::AUTHENTICATION_ERROR)->getMessage(); + } + + $session = $request->getSession(); + if ($session && $session->has(Security::AUTHENTICATION_ERROR)) { + $message = $session->get(Security::AUTHENTICATION_ERROR)->getMessage(); + $session->remove(Security::AUTHENTICATION_ERROR); + + return $message; + } + }); + + // prototypes (used by the Firewall Map) + + $app['security.context_listener._proto'] = $app->protect(function ($providerKey, $userProviders) use ($app) { + return function () use ($app, $userProviders, $providerKey) { + return new ContextListener( + $app['security.token_storage'], + $userProviders, + $providerKey, + $app['logger'], + $app['dispatcher'] + ); + }; + }); + + $app['security.user_provider.inmemory._proto'] = $app->protect(function ($params) use ($app) { + return function () use ($app, $params) { + $users = array(); + foreach ($params as $name => $user) { + $users[$name] = array('roles' => (array) $user[0], 'password' => $user[1]); + } + + return new InMemoryUserProvider($users); + }; + }); + + $app['security.exception_listener._proto'] = $app->protect(function ($entryPoint, $name, $accessDeniedHandler = null) use ($app) { + return function () use ($app, $entryPoint, $name, $accessDeniedHandler) { + return new ExceptionListener( + $app['security.token_storage'], + $app['security.trust_resolver'], + $app['security.http_utils'], + $name, + $app[$entryPoint], + null, // errorPage + $accessDeniedHandler, + $app['logger'] + ); + }; + }); + + $app['security.authentication.success_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($name, $options, $app) { + $handler = new DefaultAuthenticationSuccessHandler( + $app['security.http_utils'], + $options + ); + $handler->setProviderKey($name); + + return $handler; + }; + }); + + $app['security.authentication.failure_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($name, $options, $app) { + return new DefaultAuthenticationFailureHandler( + $app, + $app['security.http_utils'], + $options, + $app['logger'] + ); + }; + }); + + $app['security.authentication_listener.guard._proto'] = $app->protect(function ($providerKey, $options) use ($app, $that) { + return function () use ($app, $providerKey, $options, $that) { + if (!isset($app['security.authentication.guard_handler'])) { + $app['security.authentication.guard_handler'] = new GuardAuthenticatorHandler($app['security.token_storage'], $app['dispatcher']); + } + + $authenticators = array(); + foreach ($options['authenticators'] as $authenticatorId) { + $authenticators[] = $app[$authenticatorId]; + } + + return new GuardAuthenticationListener( + $app['security.authentication.guard_handler'], + $app['security.authentication_manager'], + $providerKey, + $authenticators, + $app['logger'] + ); + }; + }); + + $app['security.authentication_listener.form._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return function () use ($app, $name, $options, $that) { + $that->addFakeRoute( + 'match', + $tmp = isset($options['check_path']) ? $options['check_path'] : '/login_check', + str_replace('/', '_', ltrim($tmp, '/')) + ); + + $class = isset($options['listener_class']) ? $options['listener_class'] : 'Symfony\\Component\\Security\\Http\\Firewall\\UsernamePasswordFormAuthenticationListener'; + + if (!isset($app['security.authentication.success_handler.'.$name])) { + $app['security.authentication.success_handler.'.$name] = $app['security.authentication.success_handler._proto']($name, $options); + } + + if (!isset($app['security.authentication.failure_handler.'.$name])) { + $app['security.authentication.failure_handler.'.$name] = $app['security.authentication.failure_handler._proto']($name, $options); + } + + return new $class( + $app['security.token_storage'], + $app['security.authentication_manager'], + isset($app['security.session_strategy.'.$name]) ? $app['security.session_strategy.'.$name] : $app['security.session_strategy'], + $app['security.http_utils'], + $name, + $app['security.authentication.success_handler.'.$name], + $app['security.authentication.failure_handler.'.$name], + $options, + $app['logger'], + $app['dispatcher'], + isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null + ); + }; + }); + + $app['security.authentication_listener.http._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return function () use ($app, $providerKey, $options) { + return new BasicAuthenticationListener( + $app['security.token_storage'], + $app['security.authentication_manager'], + $providerKey, + $app['security.entry_point.'.$providerKey.'.http'], + $app['logger'] + ); + }; + }); + + $app['security.authentication_listener.anonymous._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return function () use ($app, $providerKey, $options) { + return new AnonymousAuthenticationListener( + $app['security.token_storage'], + $providerKey, + $app['logger'] + ); + }; + }); + + $app['security.authentication.logout_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($name, $options, $app) { + return new DefaultLogoutSuccessHandler( + $app['security.http_utils'], + isset($options['target_url']) ? $options['target_url'] : '/' + ); + }; + }); + + $app['security.authentication_listener.logout._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return function () use ($app, $name, $options, $that) { + $that->addFakeRoute( + 'get', + $tmp = isset($options['logout_path']) ? $options['logout_path'] : '/logout', + str_replace('/', '_', ltrim($tmp, '/')) + ); + + if (!isset($app['security.authentication.logout_handler.'.$name])) { + $app['security.authentication.logout_handler.'.$name] = $app['security.authentication.logout_handler._proto']($name, $options); + } + + $listener = new LogoutListener( + $app['security.token_storage'], + $app['security.http_utils'], + $app['security.authentication.logout_handler.'.$name], + $options, + isset($options['with_csrf']) && $options['with_csrf'] && isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null + ); + + $invalidateSession = isset($options['invalidate_session']) ? $options['invalidate_session'] : true; + if (true === $invalidateSession && false === $options['stateless']) { + $listener->addHandler(new SessionLogoutHandler()); + } + + return $listener; + }; + }); + + $app['security.authentication_listener.switch_user._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return function () use ($app, $name, $options, $that) { + return new SwitchUserListener( + $app['security.token_storage'], + $app['security.user_provider.'.$name], + $app['security.user_checker'], + $name, + $app['security.access_manager'], + $app['logger'], + isset($options['parameter']) ? $options['parameter'] : '_switch_user', + isset($options['role']) ? $options['role'] : 'ROLE_ALLOWED_TO_SWITCH', + $app['dispatcher'] + ); + }; + }); + + $app['security.entry_point.form._proto'] = $app->protect(function ($name, array $options) use ($app) { + return function () use ($app, $options) { + $loginPath = isset($options['login_path']) ? $options['login_path'] : '/login'; + $useForward = isset($options['use_forward']) ? $options['use_forward'] : false; + + return new FormAuthenticationEntryPoint($app, $app['security.http_utils'], $loginPath, $useForward); + }; + }); + + $app['security.entry_point.http._proto'] = $app->protect(function ($name, array $options) use ($app) { + return function () use ($app, $name, $options) { + return new BasicAuthenticationEntryPoint(isset($options['real_name']) ? $options['real_name'] : 'Secured'); + }; + }); + + $app['security.entry_point.guard._proto'] = $app->protect(function ($name, array $options) use ($app) { + if (isset($options['entry_point'])) { + // if it's configured explicitly, use it! + return $app[$options['entry_point']]; + } + $authenticatorIds = $options['authenticators']; + if (count($authenticatorIds) == 1) { + // if there is only one authenticator, use that as the entry point + return $app[reset($authenticatorIds)]; + } + // we have multiple entry points - we must ask them to configure one + throw new \LogicException(sprintf( + 'Because you have multiple guard configurators, you need to set the "guard.entry_point" key to one of your configurators (%s)', + implode(', ', $authenticatorIds) + )); + }); + + $app['security.authentication_provider.dao._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name) { + return new DaoAuthenticationProvider( + $app['security.user_provider.'.$name], + $app['security.user_checker'], + $name, + $app['security.encoder_factory'], + $app['security.hide_user_not_found'] + ); + }; + }); + + $app['security.authentication_provider.guard._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name, $options) { + $authenticators = array(); + foreach ($options['authenticators'] as $authenticatorId) { + $authenticators[] = $app[$authenticatorId]; + } + + return new GuardAuthenticationProvider( + $authenticators, + $app['security.user_provider.'.$name], + $name, + $app['security.user_checker'] + ); + }; + }); + + $app['security.authentication_provider.anonymous._proto'] = $app->protect(function ($name, $options) use ($app) { + return function () use ($app, $name) { + return new AnonymousAuthenticationProvider($name); + }; + }); + + if (isset($app['validator'])) { + $app['security.validator.user_password_validator'] = function ($app) { + return new UserPasswordValidator($app['security.token_storage'], $app['security.encoder_factory']); + }; + + $app['validator.validator_service_ids'] = array_merge($app['validator.validator_service_ids'], array('security.validator.user_password' => 'security.validator.user_password_validator')); + } + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['security.firewall']); + } + + public function connect(Application $app) + { + $controllers = $app['controllers_factory']; + foreach ($this->fakeRoutes as $route) { + list($method, $pattern, $name) = $route; + + $controllers->$method($pattern)->run(null)->bind($name); + } + + return $controllers; + } + + public function boot(Application $app) + { + $app->mount('/', $this->connect($app)); + } + + public function addFakeRoute($method, $pattern, $name) + { + $this->fakeRoutes[] = array($method, $pattern, $name); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php new file mode 100644 index 00000000..8986abef --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Normalizer\CustomNormalizer; +use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; + +/** + * Symfony Serializer component Provider. + * + * @author Fabien Potencier + * @author Marijn Huizendveld + */ +class SerializerServiceProvider implements ServiceProviderInterface +{ + /** + * {@inheritdoc} + * + * This method registers a serializer service. {@link http://api.symfony.com/master/Symfony/Component/Serializer/Serializer.html + * The service is provided by the Symfony Serializer component}. + */ + public function register(Container $app) + { + $app['serializer'] = function ($app) { + return new Serializer($app['serializer.normalizers'], $app['serializer.encoders']); + }; + + $app['serializer.encoders'] = function () { + return array(new JsonEncoder(), new XmlEncoder()); + }; + + $app['serializer.normalizers'] = function () { + return array(new CustomNormalizer(), new GetSetMethodNormalizer()); + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.php new file mode 100644 index 00000000..1c38adc9 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\ServiceControllerResolver; + +class ServiceControllerServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app->extend('resolver', function ($resolver, $app) { + return new ServiceControllerResolver($resolver, $app['callback_resolver']); + }); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php b/vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php new file mode 100644 index 00000000..aba4c4e8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Session/SessionListener.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Session; + +use Pimple\Container; +use Symfony\Component\HttpKernel\EventListener\SessionListener as BaseSessionListener; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier + */ +class SessionListener extends BaseSessionListener +{ + private $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + protected function getSession() + { + if (!isset($this->app['session'])) { + return; + } + + return $this->app['session']; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php b/vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php new file mode 100644 index 00000000..ab98eb12 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Session/TestSessionListener.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Session; + +use Pimple\Container; +use Symfony\Component\HttpKernel\EventListener\TestSessionListener as BaseTestSessionListener; + +/** + * Simulates sessions for testing purpose. + * + * @author Fabien Potencier + */ +class TestSessionListener extends BaseTestSessionListener +{ + private $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + protected function getSession() + { + if (!isset($this->app['session'])) { + return; + } + + return $this->app['session']; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php new file mode 100644 index 00000000..a51e230e --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Silex\Provider\Session\SessionListener; +use Silex\Provider\Session\TestSessionListener; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; + +/** + * Symfony HttpFoundation component Provider for sessions. + * + * @author Fabien Potencier + */ +class SessionServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['session.test'] = false; + + $app['session'] = function ($app) { + return new Session($app['session.storage'], $app['session.attribute_bag'], $app['session.flash_bag']); + }; + + $app['session.storage'] = function ($app) { + if ($app['session.test']) { + return $app['session.storage.test']; + } + + return $app['session.storage.native']; + }; + + $app['session.storage.handler'] = function ($app) { + return new NativeFileSessionHandler($app['session.storage.save_path']); + }; + + $app['session.storage.native'] = function ($app) { + return new NativeSessionStorage( + $app['session.storage.options'], + $app['session.storage.handler'] + ); + }; + + $app['session.listener'] = function ($app) { + return new SessionListener($app); + }; + + $app['session.storage.test'] = function () { + return new MockFileSessionStorage(); + }; + + $app['session.listener.test'] = function ($app) { + return new TestSessionListener($app); + }; + + $app['session.storage.options'] = array(); + $app['session.default_locale'] = 'en'; + $app['session.storage.save_path'] = null; + $app['session.attribute_bag'] = null; + $app['session.flash_bag'] = null; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + $dispatcher->addSubscriber($app['session.listener']); + + if ($app['session.test']) { + $app['dispatcher']->addSubscriber($app['session.listener.test']); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php new file mode 100644 index 00000000..c3dce6ce --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Api\EventListenerProviderInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; + +/** + * Swiftmailer Provider. + * + * @author Fabien Potencier + */ +class SwiftmailerServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['swiftmailer.options'] = array(); + $app['swiftmailer.use_spool'] = true; + + $app['mailer.initialized'] = false; + + $app['mailer'] = function ($app) { + $app['mailer.initialized'] = true; + $transport = $app['swiftmailer.use_spool'] ? $app['swiftmailer.spooltransport'] : $app['swiftmailer.transport']; + + return new \Swift_Mailer($transport); + }; + + $app['swiftmailer.spooltransport'] = function ($app) { + return new \Swift_Transport_SpoolTransport($app['swiftmailer.transport.eventdispatcher'], $app['swiftmailer.spool']); + }; + + $app['swiftmailer.spool'] = function ($app) { + return new \Swift_MemorySpool(); + }; + + $app['swiftmailer.transport'] = function ($app) { + $transport = new \Swift_Transport_EsmtpTransport( + $app['swiftmailer.transport.buffer'], + array($app['swiftmailer.transport.authhandler']), + $app['swiftmailer.transport.eventdispatcher'] + ); + + $options = $app['swiftmailer.options'] = array_replace(array( + 'host' => 'localhost', + 'port' => 25, + 'username' => '', + 'password' => '', + 'encryption' => null, + 'auth_mode' => null, + ), $app['swiftmailer.options']); + + $transport->setHost($options['host']); + $transport->setPort($options['port']); + $transport->setEncryption($options['encryption']); + $transport->setUsername($options['username']); + $transport->setPassword($options['password']); + $transport->setAuthMode($options['auth_mode']); + + return $transport; + }; + + $app['swiftmailer.transport.buffer'] = function () { + return new \Swift_Transport_StreamBuffer(new \Swift_StreamFilters_StringReplacementFilterFactory()); + }; + + $app['swiftmailer.transport.authhandler'] = function () { + return new \Swift_Transport_Esmtp_AuthHandler(array( + new \Swift_Transport_Esmtp_Auth_CramMd5Authenticator(), + new \Swift_Transport_Esmtp_Auth_LoginAuthenticator(), + new \Swift_Transport_Esmtp_Auth_PlainAuthenticator(), + )); + }; + + $app['swiftmailer.transport.eventdispatcher'] = function ($app) { + $dispatcher = new \Swift_Events_SimpleEventDispatcher(); + + $plugins = $app['swiftmailer.plugins']; + + if (null !== $app['swiftmailer.sender_address']) { + $plugins[] = new \Swift_Plugins_ImpersonatePlugin($app['swiftmailer.sender_address']); + } + + if (!empty($app['swiftmailer.delivery_addresses'])) { + $plugins[] = new \Swift_Plugins_RedirectingPlugin( + $app['swiftmailer.delivery_addresses'], + $app['swiftmailer.delivery_whitelist'] + ); + } + + foreach ($plugins as $plugin) { + $dispatcher->bindEventListener($plugin); + } + + return $dispatcher; + }; + + $app['swiftmailer.plugins'] = function ($app) { + return array(); + }; + + $app['swiftmailer.sender_address'] = null; + $app['swiftmailer.delivery_addresses'] = array(); + $app['swiftmailer.delivery_whitelist'] = array(); + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + // Event has no typehint as it can be either a PostResponseEvent or a ConsoleTerminateEvent + $onTerminate = function ($event) use ($app) { + // To speed things up (by avoiding Swift Mailer initialization), flush + // messages only if our mailer has been created (potentially used) + if ($app['mailer.initialized'] && $app['swiftmailer.use_spool'] && $app['swiftmailer.spooltransport'] instanceof \Swift_Transport_SpoolTransport) { + $app['swiftmailer.spooltransport']->getSpool()->flushQueue($app['swiftmailer.transport']); + } + }; + + $dispatcher->addListener(KernelEvents::TERMINATE, $onTerminate); + + if (class_exists('Symfony\Component\Console\ConsoleEvents')) { + $dispatcher->addListener(ConsoleEvents::TERMINATE, $onTerminate); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php new file mode 100644 index 00000000..0b8a8954 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\MessageSelector; +use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Translation\Loader\XliffFileLoader; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\EventListener\TranslatorListener; +use Silex\Api\EventListenerProviderInterface; + +/** + * Symfony Translation component Provider. + * + * @author Fabien Potencier + */ +class TranslationServiceProvider implements ServiceProviderInterface, EventListenerProviderInterface +{ + public function register(Container $app) + { + $app['translator'] = function ($app) { + if (!isset($app['locale'])) { + throw new \LogicException('You must define \'locale\' parameter or register the LocaleServiceProvider to use the TranslationServiceProvider'); + } + + $translator = new Translator($app['locale'], $app['translator.message_selector'], $app['translator.cache_dir'], $app['debug']); + $translator->setFallbackLocales($app['locale_fallbacks']); + $translator->addLoader('array', new ArrayLoader()); + $translator->addLoader('xliff', new XliffFileLoader()); + + if (isset($app['validator'])) { + $r = new \ReflectionClass('Symfony\Component\Validator\Validation'); + $file = dirname($r->getFilename()).'/Resources/translations/validators.'.$app['locale'].'.xlf'; + if (file_exists($file)) { + $translator->addResource('xliff', $file, $app['locale'], 'validators'); + } + } + + if (isset($app['form.factory'])) { + $r = new \ReflectionClass('Symfony\Component\Form\Form'); + $file = dirname($r->getFilename()).'/Resources/translations/validators.'.$app['locale'].'.xlf'; + if (file_exists($file)) { + $translator->addResource('xliff', $file, $app['locale'], 'validators'); + } + } + + // Register default resources + foreach ($app['translator.resources'] as $resource) { + $translator->addResource($resource[0], $resource[1], $resource[2], $resource[3]); + } + + foreach ($app['translator.domains'] as $domain => $data) { + foreach ($data as $locale => $messages) { + $translator->addResource('array', $messages, $locale, $domain); + } + } + + return $translator; + }; + + if (isset($app['request_stack'])) { + $app['translator.listener'] = function ($app) { + return new TranslatorListener($app['translator'], $app['request_stack']); + }; + } + + $app['translator.message_selector'] = function () { + return new MessageSelector(); + }; + + $app['translator.resources'] = $app->protect(function ($app) { + return array(); + }); + + $app['translator.domains'] = array(); + $app['locale_fallbacks'] = array('en'); + $app['translator.cache_dir'] = null; + } + + public function subscribe(Container $app, EventDispatcherInterface $dispatcher) + { + if (isset($app['translator.listener'])) { + $dispatcher->addSubscriber($app['translator.listener']); + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php b/vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php new file mode 100644 index 00000000..9a7aa915 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Twig/RuntimeLoader.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider\Twig; + +use Pimple\Container; + +/** + * Loads Twig extension runtimes via Pimple. + * + * @author Fabien Potencier + */ +class RuntimeLoader implements \Twig_RuntimeLoaderInterface +{ + private $container; + private $mapping; + + public function __construct(Container $container, array $mapping) + { + $this->container = $container; + $this->mapping = $mapping; + } + + /** + * {@inheritdoc} + */ + public function load($class) + { + if (isset($this->mapping[$class])) { + return $this->container[$this->mapping[$class]]; + } + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php new file mode 100644 index 00000000..f15a93bf --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Provider\Twig\RuntimeLoader; +use Symfony\Bridge\Twig\AppVariable; +use Symfony\Bridge\Twig\Extension\AssetExtension; +use Symfony\Bridge\Twig\Extension\DumpExtension; +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Extension\SecurityExtension; +use Symfony\Bridge\Twig\Extension\HttpFoundationExtension; +use Symfony\Bridge\Twig\Extension\HttpKernelExtension; +use Symfony\Bridge\Twig\Extension\WebLinkExtension; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Bridge\Twig\Form\TwigRenderer; +use Symfony\Bridge\Twig\Extension\HttpKernelRuntime; +use Symfony\Component\WebLink\HttpHeaderSerializer; + +/** + * Twig integration for Silex. + * + * @author Fabien Potencier + */ +class TwigServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['twig.options'] = array(); + $app['twig.form.templates'] = array('form_div_layout.html.twig'); + $app['twig.path'] = array(); + $app['twig.templates'] = array(); + + $app['twig.date.format'] = 'F j, Y H:i'; + $app['twig.date.interval_format'] = '%d days'; + $app['twig.date.timezone'] = null; + + $app['twig.number_format.decimals'] = 0; + $app['twig.number_format.decimal_point'] = '.'; + $app['twig.number_format.thousands_separator'] = ','; + + $app['twig'] = function ($app) { + $app['twig.options'] = array_replace( + array( + 'charset' => $app['charset'], + 'debug' => $app['debug'], + 'strict_variables' => $app['debug'], + ), $app['twig.options'] + ); + + $twig = $app['twig.environment_factory']($app); + // registered for BC, but should not be used anymore + // deprecated and should probably be removed in Silex 3.0 + $twig->addGlobal('app', $app); + + $coreExtension = $twig->getExtension('Twig_Extension_Core'); + + $coreExtension->setDateFormat($app['twig.date.format'], $app['twig.date.interval_format']); + + if (null !== $app['twig.date.timezone']) { + $coreExtension->setTimezone($app['twig.date.timezone']); + } + + $coreExtension->setNumberFormat($app['twig.number_format.decimals'], $app['twig.number_format.decimal_point'], $app['twig.number_format.thousands_separator']); + + if ($app['debug']) { + $twig->addExtension(new \Twig_Extension_Debug()); + } + + if (class_exists('Symfony\Bridge\Twig\Extension\RoutingExtension')) { + $app['twig.app_variable'] = function ($app) { + $var = new AppVariable(); + if (isset($app['security.token_storage'])) { + $var->setTokenStorage($app['security.token_storage']); + } + if (isset($app['request_stack'])) { + $var->setRequestStack($app['request_stack']); + } + $var->setDebug($app['debug']); + + return $var; + }; + + $twig->addGlobal('global', $app['twig.app_variable']); + + if (isset($app['request_stack'])) { + $twig->addExtension(new HttpFoundationExtension($app['request_stack'])); + $twig->addExtension(new RoutingExtension($app['url_generator'])); + } + + if (isset($app['translator'])) { + $twig->addExtension(new TranslationExtension($app['translator'])); + } + + if (isset($app['security.authorization_checker'])) { + $twig->addExtension(new SecurityExtension($app['security.authorization_checker'])); + } + + if (isset($app['fragment.handler'])) { + $app['fragment.renderer.hinclude']->setTemplating($twig); + + $twig->addExtension(new HttpKernelExtension($app['fragment.handler'])); + } + + if (isset($app['assets.packages'])) { + $twig->addExtension(new AssetExtension($app['assets.packages'])); + } + + if (isset($app['form.factory'])) { + $app['twig.form.engine'] = function ($app) use ($twig) { + return new TwigRendererEngine($app['twig.form.templates'], $twig); + }; + + $app['twig.form.renderer'] = function ($app) { + $csrfTokenManager = isset($app['csrf.token_manager']) ? $app['csrf.token_manager'] : null; + + return new TwigRenderer($app['twig.form.engine'], $csrfTokenManager); + }; + + $twig->addExtension(new FormExtension(class_exists(HttpKernelRuntime::class) ? null : $app['twig.form.renderer'])); + + // add loader for Symfony built-in form templates + $reflected = new \ReflectionClass('Symfony\Bridge\Twig\Extension\FormExtension'); + $path = dirname($reflected->getFileName()).'/../Resources/views/Form'; + $app['twig.loader']->addLoader(new \Twig_Loader_Filesystem($path)); + } + + if (isset($app['var_dumper.cloner'])) { + $twig->addExtension(new DumpExtension($app['var_dumper.cloner'])); + } + + if (class_exists(HttpKernelRuntime::class)) { + $twig->addRuntimeLoader($app['twig.runtime_loader']); + } + + if (class_exists(HttpHeaderSerializer::class) && class_exists(WebLinkExtension::class)) { + $twig->addExtension(new WebLinkExtension($app['request_stack'])); + } + } + + return $twig; + }; + + $app['twig.loader.filesystem'] = function ($app) { + return new \Twig_Loader_Filesystem($app['twig.path']); + }; + + $app['twig.loader.array'] = function ($app) { + return new \Twig_Loader_Array($app['twig.templates']); + }; + + $app['twig.loader'] = function ($app) { + return new \Twig_Loader_Chain(array( + $app['twig.loader.array'], + $app['twig.loader.filesystem'], + )); + }; + + $app['twig.environment_factory'] = $app->protect(function ($app) { + return new \Twig_Environment($app['twig.loader'], $app['twig.options']); + }); + + $app['twig.runtime.httpkernel'] = function ($app) { + return new HttpKernelRuntime($app['fragment.handler']); + }; + + $app['twig.runtimes'] = function ($app) { + return array( + HttpKernelRuntime::class => 'twig.runtime.httpkernel', + TwigRenderer::class => 'twig.form.renderer', + ); + }; + + $app['twig.runtime_loader'] = function ($app) { + return new RuntimeLoader($app, $app['twig.runtimes']); + }; + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.php b/vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.php new file mode 100644 index 00000000..9f5e499d --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/Validator/ConstraintValidatorFactory.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 Silex\Provider\Validator; + +use Pimple\Container; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidatorFactory as BaseConstraintValidatorFactory; + +/** + * Uses a service container to create constraint validators with dependencies. + * + * @author Kris Wallsmith + * @author Alex Kalyvitis + */ +class ConstraintValidatorFactory extends BaseConstraintValidatorFactory +{ + /** + * @var Container + */ + protected $container; + + /** + * @var array + */ + protected $serviceNames; + + /** + * Constructor. + * + * @param Container $container DI container + * @param array $serviceNames Validator service names + */ + public function __construct(Container $container, array $serviceNames = array(), $propertyAccessor = null) + { + parent::__construct($propertyAccessor); + + $this->container = $container; + $this->serviceNames = $serviceNames; + } + + /** + * {@inheritdoc} + */ + public function getInstance(Constraint $constraint) + { + $name = $constraint->validatedBy(); + + if (isset($this->serviceNames[$name])) { + return $this->container[$this->serviceNames[$name]]; + } + + return parent::getInstance($constraint); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php new file mode 100644 index 00000000..d89a3cb5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.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 Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Provider\Validator\ConstraintValidatorFactory; +use Symfony\Component\Validator\Validator; +use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; +use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; +use Symfony\Component\Validator\Validation; + +/** + * Symfony Validator component Provider. + * + * @author Fabien Potencier + */ +class ValidatorServiceProvider implements ServiceProviderInterface +{ + public function register(Container $app) + { + $app['validator'] = function ($app) { + return $app['validator.builder']->getValidator(); + }; + + $app['validator.builder'] = function ($app) { + $builder = Validation::createValidatorBuilder(); + $builder->setConstraintValidatorFactory($app['validator.validator_factory']); + $builder->setTranslationDomain('validators'); + $builder->addObjectInitializers($app['validator.object_initializers']); + $builder->setMetadataFactory($app['validator.mapping.class_metadata_factory']); + if (isset($app['translator'])) { + $builder->setTranslator($app['translator']); + } + + return $builder; + }; + + $app['validator.mapping.class_metadata_factory'] = function ($app) { + return new LazyLoadingMetadataFactory(new StaticMethodLoader()); + }; + + $app['validator.validator_factory'] = function () use ($app) { + return new ConstraintValidatorFactory($app, $app['validator.validator_service_ids']); + }; + + $app['validator.object_initializers'] = function ($app) { + return array(); + }; + + $app['validator.validator_service_ids'] = array(); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php new file mode 100644 index 00000000..7c40b5ee --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/VarDumperServiceProvider.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Silex\Application; +use Silex\Api\BootableProviderInterface; +use Symfony\Component\VarDumper\VarDumper; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * Symfony Var Dumper component Provider. + * + * @author Fabien Potencier + */ +class VarDumperServiceProvider implements ServiceProviderInterface, BootableProviderInterface +{ + public function register(Container $app) + { + $app['var_dumper.cli_dumper'] = function ($app) { + return new CliDumper($app['var_dumper.dump_destination'], $app['charset']); + }; + + $app['var_dumper.cloner'] = function ($app) { + return new VarCloner(); + }; + + $app['var_dumper.dump_destination'] = null; + } + + public function boot(Application $app) + { + if (!$app['debug']) { + return; + } + + // This code is here to lazy load the dump stack. This default + // configuration for CLI mode is overridden in HTTP mode on + // 'kernel.request' event + VarDumper::setHandler(function ($var) use ($app) { + VarDumper::setHandler($handler = function ($var) use ($app) { + $app['var_dumper.cli_dumper']->dump($app['var_dumper.cloner']->cloneVar($var)); + }); + $handler($var); + }); + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/composer.json b/vendor/silex/silex/src/Silex/Provider/composer.json new file mode 100644 index 00000000..99b7a491 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/composer.json @@ -0,0 +1,31 @@ +{ + "minimum-stability": "dev", + "name": "silex/providers", + "description": "The Silex providers", + "keywords": ["microframework"], + "homepage": "http://silex.sensiolabs.org", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0", + "silex/api": "~2.1" + }, + "autoload": { + "psr-4": { "Silex\\Provider\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/vendor/silex/silex/src/Silex/Route.php b/vendor/silex/silex/src/Silex/Route.php new file mode 100644 index 00000000..99e82d87 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Route.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 Silex; + +use Symfony\Component\Routing\Route as BaseRoute; + +/** + * A wrapper for a controller, mapped to a route. + * + * @author Fabien Potencier + */ +class Route extends BaseRoute +{ + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string $host The host pattern to match + * @param string|array $schemes A required URI scheme or an array of restricted schemes + * @param string|array $methods A required HTTP method or an array of restricted methods + */ + public function __construct($path = '/', array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array()) + { + // overridden constructor to make $path optional + parent::__construct($path, $defaults, $requirements, $options, $host, $schemes, $methods); + } + + /** + * Sets the route code that should be executed when matched. + * + * @param callable $to PHP callback that returns the response when matched + * + * @return Route $this The current Route instance + */ + public function run($to) + { + $this->setDefault('_controller', $to); + + return $this; + } + + /** + * Sets the requirement for a route variable. + * + * @param string $variable The variable name + * @param string $regexp The regexp to apply + * + * @return Route $this The current route instance + */ + public function assert($variable, $regexp) + { + $this->setRequirement($variable, $regexp); + + return $this; + } + + /** + * Sets the default value for a route variable. + * + * @param string $variable The variable name + * @param mixed $default The default value + * + * @return Route $this The current Route instance + */ + public function value($variable, $default) + { + $this->setDefault($variable, $default); + + return $this; + } + + /** + * Sets a converter for a route variable. + * + * @param string $variable The variable name + * @param mixed $callback A PHP callback that converts the original value + * + * @return Route $this The current Route instance + */ + public function convert($variable, $callback) + { + $converters = $this->getOption('_converters'); + $converters[$variable] = $callback; + $this->setOption('_converters', $converters); + + return $this; + } + + /** + * Sets the requirement for the HTTP method. + * + * @param string $method The HTTP method name. Multiple methods can be supplied, delimited by a pipe character '|', eg. 'GET|POST' + * + * @return Route $this The current Route instance + */ + public function method($method) + { + $this->setMethods(explode('|', $method)); + + return $this; + } + + /** + * Sets the requirement of host on this Route. + * + * @param string $host The host for which this route should be enabled + * + * @return Route $this The current Route instance + */ + public function host($host) + { + $this->setHost($host); + + return $this; + } + + /** + * Sets the requirement of HTTP (no HTTPS) on this Route. + * + * @return Route $this The current Route instance + */ + public function requireHttp() + { + $this->setSchemes('http'); + + return $this; + } + + /** + * Sets the requirement of HTTPS on this Route. + * + * @return Route $this The current Route instance + */ + public function requireHttps() + { + $this->setSchemes('https'); + + return $this; + } + + /** + * Sets a callback to handle before triggering the route callback. + * + * @param mixed $callback A PHP callback to be triggered when the Route is matched, just before the route callback + * + * @return Route $this The current Route instance + */ + public function before($callback) + { + $callbacks = $this->getOption('_before_middlewares'); + $callbacks[] = $callback; + $this->setOption('_before_middlewares', $callbacks); + + return $this; + } + + /** + * Sets a callback to handle after the route callback. + * + * @param mixed $callback A PHP callback to be triggered after the route callback + * + * @return Route $this The current Route instance + */ + public function after($callback) + { + $callbacks = $this->getOption('_after_middlewares'); + $callbacks[] = $callback; + $this->setOption('_after_middlewares', $callbacks); + + return $this; + } + + /** + * Sets a condition for the route to match. + * + * @param string $condition The condition + * + * @return Route $this The current Route instance + */ + public function when($condition) + { + $this->setCondition($condition); + + return $this; + } +} diff --git a/vendor/silex/silex/src/Silex/Route/SecurityTrait.php b/vendor/silex/silex/src/Silex/Route/SecurityTrait.php new file mode 100644 index 00000000..d42ba2fb --- /dev/null +++ b/vendor/silex/silex/src/Silex/Route/SecurityTrait.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Route; + +use Symfony\Component\Security\Core\Exception\AccessDeniedException; + +/** + * Security trait. + * + * @author Fabien Potencier + */ +trait SecurityTrait +{ + public function secure($roles) + { + $this->before(function ($request, $app) use ($roles) { + if (!$app['security.authorization_checker']->isGranted($roles)) { + throw new AccessDeniedException(); + } + }); + } +} diff --git a/vendor/silex/silex/src/Silex/ServiceControllerResolver.php b/vendor/silex/silex/src/Silex/ServiceControllerResolver.php new file mode 100644 index 00000000..87f91b04 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ServiceControllerResolver.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; + +/** + * Enables name_of_service:method_name syntax for declaring controllers. + * + * @link http://silex.sensiolabs.org/doc/providers/service_controller.html + */ +class ServiceControllerResolver implements ControllerResolverInterface +{ + protected $controllerResolver; + protected $callbackResolver; + + /** + * Constructor. + * + * @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface instance to delegate to + * @param CallbackResolver $callbackResolver A service resolver instance + */ + public function __construct(ControllerResolverInterface $controllerResolver, CallbackResolver $callbackResolver) + { + $this->controllerResolver = $controllerResolver; + $this->callbackResolver = $callbackResolver; + } + + /** + * {@inheritdoc} + */ + public function getController(Request $request) + { + $controller = $request->attributes->get('_controller', null); + + if (!$this->callbackResolver->isValid($controller)) { + return $this->controllerResolver->getController($request); + } + + return $this->callbackResolver->convertCallback($controller); + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + return $this->controllerResolver->getArguments($request, $controller); + } +} diff --git a/vendor/silex/silex/src/Silex/ViewListenerWrapper.php b/vendor/silex/silex/src/Silex/ViewListenerWrapper.php new file mode 100644 index 00000000..a67ec938 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ViewListenerWrapper.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; + +/** + * Wraps view listeners. + * + * @author Dave Marshall + */ +class ViewListenerWrapper +{ + private $app; + private $callback; + + /** + * Constructor. + * + * @param Application $app An Application instance + * @param mixed $callback + */ + public function __construct(Application $app, $callback) + { + $this->app = $app; + $this->callback = $callback; + } + + public function __invoke(GetResponseForControllerResultEvent $event) + { + $controllerResult = $event->getControllerResult(); + $callback = $this->app['callback_resolver']->resolveCallback($this->callback); + + if (!$this->shouldRun($callback, $controllerResult)) { + return; + } + + $response = call_user_func($callback, $controllerResult, $event->getRequest()); + + if ($response instanceof Response) { + $event->setResponse($response); + } elseif (null !== $response) { + $event->setControllerResult($response); + } + } + + private function shouldRun($callback, $controllerResult) + { + if (is_array($callback)) { + $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); + } elseif (is_object($callback) && !$callback instanceof \Closure) { + $callbackReflection = new \ReflectionObject($callback); + $callbackReflection = $callbackReflection->getMethod('__invoke'); + } else { + $callbackReflection = new \ReflectionFunction($callback); + } + + if ($callbackReflection->getNumberOfParameters() > 0) { + $parameters = $callbackReflection->getParameters(); + $expectedControllerResult = $parameters[0]; + + if ($expectedControllerResult->getClass() && (!is_object($controllerResult) || !$expectedControllerResult->getClass()->isInstance($controllerResult))) { + return false; + } + + if ($expectedControllerResult->isArray() && !is_array($controllerResult)) { + return false; + } + + if (method_exists($expectedControllerResult, 'isCallable') && $expectedControllerResult->isCallable() && !is_callable($controllerResult)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/silex/silex/src/Silex/WebTestCase.php b/vendor/silex/silex/src/Silex/WebTestCase.php new file mode 100644 index 00000000..644bb050 --- /dev/null +++ b/vendor/silex/silex/src/Silex/WebTestCase.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 Silex; + +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * WebTestCase is the base class for functional tests. + * + * @author Igor Wiedler + */ +abstract class WebTestCase extends \PHPUnit_Framework_TestCase +{ + /** + * HttpKernelInterface instance. + * + * @var HttpKernelInterface + */ + protected $app; + + /** + * PHPUnit setUp for setting up the application. + * + * Note: Child classes that define a setUp method must call + * parent::setUp(). + */ + protected function setUp() + { + $this->app = $this->createApplication(); + } + + /** + * Creates the application. + * + * @return HttpKernelInterface + */ + abstract public function createApplication(); + + /** + * Creates a Client. + * + * @param array $server Server parameters + * + * @return Client A Client instance + */ + public function createClient(array $server = array()) + { + if (!class_exists('Symfony\Component\BrowserKit\Client')) { + throw new \LogicException('Component "symfony/browser-kit" is required by WebTestCase.'.PHP_EOL.'Run composer require symfony/browser-kit'); + } + + return new Client($this->app, $server); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php new file mode 100644 index 00000000..5851a4c9 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/FormApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class FormApplication extends Application +{ + use Application\FormTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php new file mode 100644 index 00000000..74e56c05 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\FormServiceProvider; + +/** + * FormTrait test cases. + * + * @author Fabien Potencier + */ +class FormTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testForm() + { + $this->assertInstanceOf('Symfony\Component\Form\FormBuilder', $this->createApplication()->form()); + } + + public function createApplication() + { + $app = new FormApplication(); + $app->register(new FormServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php new file mode 100644 index 00000000..9fec12fb --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/MonologApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class MonologApplication extends Application +{ + use Application\MonologTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php new file mode 100644 index 00000000..a2e3acbf --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\MonologServiceProvider; +use Monolog\Handler\TestHandler; +use Monolog\Logger; + +/** + * MonologTrait test cases. + * + * @author Fabien Potencier + */ +class MonologTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testLog() + { + $app = $this->createApplication(); + + $app->log('Foo'); + $app->log('Bar', array(), Logger::DEBUG); + $this->assertTrue($app['monolog.handler']->hasInfo('Foo')); + $this->assertTrue($app['monolog.handler']->hasDebug('Bar')); + } + + public function createApplication() + { + $app = new MonologApplication(); + $app->register(new MonologServiceProvider(), array( + 'monolog.handler' => function () use ($app) { + return new TestHandler($app['monolog.level']); + }, + 'monolog.logfile' => 'php://memory', + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php new file mode 100644 index 00000000..dc85999e --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class SecurityApplication extends Application +{ + use Application\SecurityTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php new file mode 100644 index 00000000..e91eda73 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php @@ -0,0 +1,90 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\SecurityServiceProvider; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityTrait test cases. + * + * @author Fabien Potencier + */ +class SecurityTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testEncodePassword() + { + $app = $this->createApplication(array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + )); + + $user = new User('foo', 'bar'); + $password = 'foo'; + $encoded = $app->encodePassword($user, $password); + + $this->assertTrue( + $app['security.encoder_factory']->getEncoder($user)->isPasswordValid($encoded, $password, $user->getSalt()) + ); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException + */ + public function testIsGrantedWithoutTokenThrowsException() + { + $app = $this->createApplication(); + $app->get('/', function () { return 'foo'; }); + $app->handle(Request::create('/')); + $app->isGranted('ROLE_ADMIN'); + } + + public function testIsGranted() + { + $request = Request::create('/'); + + $app = $this->createApplication(array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + 'monique' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + )); + $app->get('/', function () { return 'foo'; }); + + // User is Monique (ROLE_USER) + $request->headers->set('PHP_AUTH_USER', 'monique'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $app->handle($request); + $this->assertTrue($app->isGranted('ROLE_USER')); + $this->assertFalse($app->isGranted('ROLE_ADMIN')); + + // User is Fabien (ROLE_ADMIN) + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $app->handle($request); + $this->assertFalse($app->isGranted('ROLE_USER')); + $this->assertTrue($app->isGranted('ROLE_ADMIN')); + } + + public function createApplication($users = array()) + { + $app = new SecurityApplication(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + 'users' => $users, + ), + ), + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php new file mode 100644 index 00000000..6a28d539 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class SwiftmailerApplication extends Application +{ + use Application\SwiftmailerTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php new file mode 100644 index 00000000..923db39e --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.php @@ -0,0 +1,44 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\SwiftmailerServiceProvider; + +/** + * SwiftmailerTrait test cases. + * + * @author Fabien Potencier + */ +class SwiftmailerTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testMail() + { + $app = $this->createApplication(); + + $message = $this->getMockBuilder('Swift_Message')->disableOriginalConstructor()->getMock(); + $app['mailer'] = $mailer = $this->getMockBuilder('Swift_Mailer')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once()) + ->method('send') + ->with($message) + ; + + $app->mail($message); + } + + public function createApplication() + { + $app = new SwiftmailerApplication(); + $app->register(new SwiftmailerServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php new file mode 100644 index 00000000..3e51b9c2 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class TranslationApplication extends Application +{ + use Application\TranslationTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php new file mode 100644 index 00000000..f2837c14 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.php @@ -0,0 +1,46 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\TranslationServiceProvider; + +/** + * TranslationTrait test cases. + * + * @author Fabien Potencier + */ +class TranslationTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testTrans() + { + $app = $this->createApplication(); + $app['translator'] = $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator')->disableOriginalConstructor()->getMock(); + $translator->expects($this->once())->method('trans'); + $app->trans('foo'); + } + + public function testTransChoice() + { + $app = $this->createApplication(); + $app['translator'] = $translator = $this->getMockBuilder('Symfony\Component\Translation\Translator')->disableOriginalConstructor()->getMock(); + $translator->expects($this->once())->method('transChoice'); + $app->transChoice('foo', 2); + } + + public function createApplication() + { + $app = new TranslationApplication(); + $app->register(new TranslationServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php new file mode 100644 index 00000000..f1bb4738 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TwigApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class TwigApplication extends Application +{ + use Application\TwigTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php new file mode 100644 index 00000000..9435f7c8 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php @@ -0,0 +1,80 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\TwigServiceProvider; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * TwigTrait test cases. + * + * @author Fabien Potencier + */ +class TwigTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testRender() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('render')->will($this->returnValue('foo')); + + $response = $app->render('view'); + $this->assertEquals('Symfony\Component\HttpFoundation\Response', get_class($response)); + $this->assertEquals('foo', $response->getContent()); + } + + public function testRenderKeepResponse() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('render')->will($this->returnValue('foo')); + + $response = $app->render('view', array(), new Response('', 404)); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testRenderForStream() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('display')->will($this->returnCallback(function () { echo 'foo'; })); + + $response = $app->render('view', array(), new StreamedResponse()); + $this->assertEquals('Symfony\Component\HttpFoundation\StreamedResponse', get_class($response)); + + ob_start(); + $response->send(); + $this->assertEquals('foo', ob_get_clean()); + } + + public function testRenderView() + { + $app = $this->createApplication(); + + $app['twig'] = $mailer = $this->getMockBuilder('Twig_Environment')->disableOriginalConstructor()->getMock(); + $mailer->expects($this->once())->method('render'); + + $app->renderView('view'); + } + + public function createApplication() + { + $app = new TwigApplication(); + $app->register(new TwigServiceProvider()); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php new file mode 100644 index 00000000..4239af46 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorApplication.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Application; + +class UrlGeneratorApplication extends Application +{ + use Application\UrlGeneratorTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php new file mode 100644 index 00000000..822c6eb3 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * UrlGeneratorTrait test cases. + * + * @author Fabien Potencier + */ +class UrlGeneratorTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testUrl() + { + $app = new UrlGeneratorApplication(); + $app['url_generator'] = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->disableOriginalConstructor()->getMock(); + $app['url_generator']->expects($this->once())->method('generate')->with('foo', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $app->url('foo'); + } + + public function testPath() + { + $app = new UrlGeneratorApplication(); + $app['url_generator'] = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->disableOriginalConstructor()->getMock(); + $app['url_generator']->expects($this->once())->method('generate')->with('foo', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + $app->path('foo'); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php b/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php new file mode 100644 index 00000000..9a23cd1d --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php @@ -0,0 +1,719 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Fig\Link\GenericLinkProvider; +use Fig\Link\Link; +use Silex\Application; +use Silex\ControllerCollection; +use Silex\Api\ControllerProviderInterface; +use Silex\Route; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Routing\RouteCollection; + +/** + * Application test cases. + * + * @author Igor Wiedler + */ +class ApplicationTest extends \PHPUnit_Framework_TestCase +{ + public function testMatchReturnValue() + { + $app = new Application(); + + $returnValue = $app->match('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->get('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->post('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->put('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->patch('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + + $returnValue = $app->delete('/foo', function () {}); + $this->assertInstanceOf('Silex\Controller', $returnValue); + } + + public function testConstructorInjection() + { + // inject a custom parameter + $params = array('param' => 'value'); + $app = new Application($params); + $this->assertSame($params['param'], $app['param']); + + // inject an existing parameter + $params = array('locale' => 'value'); + $app = new Application($params); + $this->assertSame($params['locale'], $app['locale']); + } + + public function testGetRequest() + { + $request = Request::create('/'); + + $app = new Application(); + $app->get('/', function (Request $req) use ($request) { + return $request === $req ? 'ok' : 'ko'; + }); + + $this->assertEquals('ok', $app->handle($request)->getContent()); + } + + public function testGetRoutesWithNoRoutes() + { + $app = new Application(); + + $routes = $app['routes']; + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); + $this->assertEquals(0, count($routes->all())); + } + + public function testGetRoutesWithRoutes() + { + $app = new Application(); + + $app->get('/foo', function () { + return 'foo'; + }); + + $app->get('/bar')->run(function () { + return 'bar'; + }); + + $routes = $app['routes']; + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); + $this->assertEquals(0, count($routes->all())); + $app->flush(); + $this->assertEquals(2, count($routes->all())); + } + + public function testOnCoreController() + { + $app = new Application(); + + $app->get('/foo/{foo}', function (\ArrayObject $foo) { + return $foo['foo']; + })->convert('foo', function ($foo) { return new \ArrayObject(array('foo' => $foo)); }); + + $response = $app->handle(Request::create('/foo/bar')); + $this->assertEquals('bar', $response->getContent()); + + $app->get('/foo/{foo}/{bar}', function (\ArrayObject $foo) { + return $foo['foo']; + })->convert('foo', function ($foo, Request $request) { return new \ArrayObject(array('foo' => $foo.$request->attributes->get('bar'))); }); + + $response = $app->handle(Request::create('/foo/foo/bar')); + $this->assertEquals('foobar', $response->getContent()); + } + + public function testOn() + { + $app = new Application(); + $app['pass'] = false; + + $app->on('test', function (Event $e) use ($app) { + $app['pass'] = true; + }); + + $app['dispatcher']->dispatch('test'); + + $this->assertTrue($app['pass']); + } + + public function testAbort() + { + $app = new Application(); + + try { + $app->abort(404); + $this->fail(); + } catch (HttpException $e) { + $this->assertEquals(404, $e->getStatusCode()); + } + } + + /** + * @dataProvider escapeProvider + */ + public function testEscape($expected, $text) + { + $app = new Application(); + + $this->assertEquals($expected, $app->escape($text)); + } + + public function escapeProvider() + { + return array( + array('<', '<'), + array('>', '>'), + array('"', '"'), + array("'", "'"), + array('abc', 'abc'), + ); + } + + public function testControllersAsMethods() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->get('/{name}', 'Silex\Tests\FooController::barAction'); + + $this->assertEquals('Hello Fabien', $app->handle(Request::create('/Fabien'))->getContent()); + } + + public function testApplicationTypeHintWorks() + { + $app = new SpecialApplication(); + unset($app['exception_handler']); + + $app->get('/{name}', 'Silex\Tests\FooController::barSpecialAction'); + + $this->assertEquals('Hello Fabien in Silex\Tests\SpecialApplication', $app->handle(Request::create('/Fabien'))->getContent()); + } + + /** + * @requires PHP 7.0 + */ + public function testPhp7TypeHintWorks() + { + $app = new SpecialApplication(); + unset($app['exception_handler']); + + $app->get('/{name}', 'Silex\Tests\Fixtures\Php7Controller::typehintedAction'); + + $this->assertEquals('Hello Fabien in Silex\Tests\SpecialApplication', $app->handle(Request::create('/Fabien'))->getContent()); + } + + public function testHttpSpec() + { + $app = new Application(); + $app['charset'] = 'ISO-8859-1'; + + $app->get('/', function () { + return 'hello'; + }); + + // content is empty for HEAD requests + $response = $app->handle(Request::create('/', 'HEAD')); + $this->assertEquals('', $response->getContent()); + + // charset is appended to Content-Type + $response = $app->handle(Request::create('/')); + + $this->assertEquals('text/html; charset=ISO-8859-1', $response->headers->get('Content-Type')); + } + + public function testRoutesMiddlewares() + { + $app = new Application(); + + $test = $this; + + $middlewareTarget = array(); + $beforeMiddleware1 = function (Request $request) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'before_middleware1_triggered'; + }; + $beforeMiddleware2 = function (Request $request) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'before_middleware2_triggered'; + }; + $beforeMiddleware3 = function (Request $request) use (&$middlewareTarget, $test) { + throw new \Exception('This middleware shouldn\'t run!'); + }; + + $afterMiddleware1 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'after_middleware1_triggered'; + }; + $afterMiddleware2 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { + $test->assertEquals('/reached', $request->getRequestUri()); + $middlewareTarget[] = 'after_middleware2_triggered'; + }; + $afterMiddleware3 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { + throw new \Exception('This middleware shouldn\'t run!'); + }; + + $app->get('/reached', function () use (&$middlewareTarget) { + $middlewareTarget[] = 'route_triggered'; + + return 'hello'; + }) + ->before($beforeMiddleware1) + ->before($beforeMiddleware2) + ->after($afterMiddleware1) + ->after($afterMiddleware2); + + $app->get('/never-reached', function () use (&$middlewareTarget) { + throw new \Exception('This route shouldn\'t run!'); + }) + ->before($beforeMiddleware3) + ->after($afterMiddleware3); + + $result = $app->handle(Request::create('/reached')); + + $this->assertSame(array('before_middleware1_triggered', 'before_middleware2_triggered', 'route_triggered', 'after_middleware1_triggered', 'after_middleware2_triggered'), $middlewareTarget); + $this->assertEquals('hello', $result->getContent()); + } + + public function testRoutesBeforeMiddlewaresWithResponseObject() + { + $app = new Application(); + + $app->get('/foo', function () { + throw new \Exception('This route shouldn\'t run!'); + }) + ->before(function () { + return new Response('foo'); + }); + + $request = Request::create('/foo'); + $result = $app->handle($request); + + $this->assertEquals('foo', $result->getContent()); + } + + public function testRoutesAfterMiddlewaresWithResponseObject() + { + $app = new Application(); + + $app->get('/foo', function () { + return new Response('foo'); + }) + ->after(function () { + return new Response('bar'); + }); + + $request = Request::create('/foo'); + $result = $app->handle($request); + + $this->assertEquals('bar', $result->getContent()); + } + + public function testRoutesBeforeMiddlewaresWithRedirectResponseObject() + { + $app = new Application(); + + $app->get('/foo', function () { + throw new \Exception('This route shouldn\'t run!'); + }) + ->before(function () use ($app) { + return $app->redirect('/bar'); + }); + + $request = Request::create('/foo'); + $result = $app->handle($request); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result); + $this->assertEquals('/bar', $result->getTargetUrl()); + } + + public function testRoutesBeforeMiddlewaresTriggeredAfterSilexBeforeFilters() + { + $app = new Application(); + + $middlewareTarget = array(); + $middleware = function (Request $request) use (&$middlewareTarget) { + $middlewareTarget[] = 'middleware_triggered'; + }; + + $app->get('/foo', function () use (&$middlewareTarget) { + $middlewareTarget[] = 'route_triggered'; + }) + ->before($middleware); + + $app->before(function () use (&$middlewareTarget) { + $middlewareTarget[] = 'before_triggered'; + }); + + $app->handle(Request::create('/foo')); + + $this->assertSame(array('before_triggered', 'middleware_triggered', 'route_triggered'), $middlewareTarget); + } + + public function testRoutesAfterMiddlewaresTriggeredBeforeSilexAfterFilters() + { + $app = new Application(); + + $middlewareTarget = array(); + $middleware = function (Request $request) use (&$middlewareTarget) { + $middlewareTarget[] = 'middleware_triggered'; + }; + + $app->get('/foo', function () use (&$middlewareTarget) { + $middlewareTarget[] = 'route_triggered'; + }) + ->after($middleware); + + $app->after(function () use (&$middlewareTarget) { + $middlewareTarget[] = 'after_triggered'; + }); + + $app->handle(Request::create('/foo')); + + $this->assertSame(array('route_triggered', 'middleware_triggered', 'after_triggered'), $middlewareTarget); + } + + public function testFinishFilter() + { + $containerTarget = array(); + + $app = new Application(); + + $app->finish(function () use (&$containerTarget) { + $containerTarget[] = '4_filterFinish'; + }); + + $app->get('/foo', function () use (&$containerTarget) { + $containerTarget[] = '1_routeTriggered'; + + return new StreamedResponse(function () use (&$containerTarget) { + $containerTarget[] = '3_responseSent'; + }); + }); + + $app->after(function () use (&$containerTarget) { + $containerTarget[] = '2_filterAfter'; + }); + + $app->run(Request::create('/foo')); + + $this->assertSame(array('1_routeTriggered', '2_filterAfter', '3_responseSent', '4_filterFinish'), $containerTarget); + } + + /** + * @expectedException \RuntimeException + */ + public function testNonResponseAndNonNullReturnFromRouteBeforeMiddlewareShouldThrowRuntimeException() + { + $app = new Application(); + + $middleware = function (Request $request) { + return 'string return'; + }; + + $app->get('/', function () { + return 'hello'; + }) + ->before($middleware); + + $app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false); + } + + /** + * @expectedException \RuntimeException + */ + public function testNonResponseAndNonNullReturnFromRouteAfterMiddlewareShouldThrowRuntimeException() + { + $app = new Application(); + + $middleware = function (Request $request) { + return 'string return'; + }; + + $app->get('/', function () { + return 'hello'; + }) + ->after($middleware); + + $app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false); + } + + public function testSubRequest() + { + $app = new Application(); + $app->get('/sub', function (Request $request) { + return new Response('foo'); + }); + $app->get('/', function (Request $request) use ($app) { + return $app->handle(Request::create('/sub'), HttpKernelInterface::SUB_REQUEST); + }); + + $this->assertEquals('foo', $app->handle(Request::create('/'))->getContent()); + } + + public function testRegisterShouldReturnSelf() + { + $app = new Application(); + $provider = $this->getMockBuilder('Pimple\ServiceProviderInterface')->getMock(); + + $this->assertSame($app, $app->register($provider)); + } + + public function testMountShouldReturnSelf() + { + $app = new Application(); + $mounted = new ControllerCollection(new Route()); + $mounted->get('/{name}', function ($name) { return new Response($name); }); + + $this->assertSame($app, $app->mount('/hello', $mounted)); + } + + public function testMountPreservesOrder() + { + $app = new Application(); + $mounted = new ControllerCollection(new Route()); + $mounted->get('/mounted')->bind('second'); + + $app->get('/before')->bind('first'); + $app->mount('/', $mounted); + $app->get('/after')->bind('third'); + $app->flush(); + + $this->assertEquals(array('first', 'second', 'third'), array_keys(iterator_to_array($app['routes']))); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable. + */ + public function testMountNullException() + { + $app = new Application(); + $app->mount('/exception', null); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The method "Silex\Tests\IncorrectControllerCollection::connect" must return a "ControllerCollection" instance. Got: "NULL" + */ + public function testMountWrongConnectReturnValueException() + { + $app = new Application(); + $app->mount('/exception', new IncorrectControllerCollection()); + } + + public function testMountCallable() + { + $app = new Application(); + $app->mount('/prefix', function (ControllerCollection $coll) { + $coll->get('/path'); + }); + + $app->flush(); + + $this->assertEquals(1, $app['routes']->count()); + } + + public function testSendFile() + { + $app = new Application(); + + $response = $app->sendFile(__FILE__, 200, array('Content-Type: application/php')); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\BinaryFileResponse', $response); + $this->assertEquals(__FILE__, (string) $response->getFile()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "homepage" route must have code to run when it matches. + */ + public function testGetRouteCollectionWithRouteWithoutController() + { + $app = new Application(); + unset($app['exception_handler']); + $app->match('/')->bind('homepage'); + $app->handle(Request::create('/')); + } + + public function testBeforeFilterOnMountedControllerGroupIsolatedToGroup() + { + $app = new Application(); + $app->match('/', function () { return new Response('ok'); }); + $mounted = $app['controllers_factory']; + $mounted->before(function () { return new Response('not ok'); }); + $app->mount('/group', $mounted); + + $response = $app->handle(Request::create('/')); + $this->assertEquals('ok', $response->getContent()); + } + + public function testViewListenerWithPrimitive() + { + $app = new Application(); + $app->get('/foo', function () { return 123; }); + $app->view(function ($view, Request $request) { + return new Response($view); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('123', $response->getContent()); + } + + public function testViewListenerWithArrayTypeHint() + { + $app = new Application(); + $app->get('/foo', function () { return array('ok'); }); + $app->view(function (array $view) { + return new Response($view[0]); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('ok', $response->getContent()); + } + + public function testViewListenerWithObjectTypeHint() + { + $app = new Application(); + $app->get('/foo', function () { return (object) array('name' => 'world'); }); + $app->view(function (\stdClass $view) { + return new Response('Hello '.$view->name); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenerWithCallableTypeHint() + { + $app = new Application(); + $app->get('/foo', function () { return function () { return 'world'; }; }); + $app->view(function (callable $view) { + return new Response('Hello '.$view()); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenersCanBeChained() + { + $app = new Application(); + $app->get('/foo', function () { return (object) array('name' => 'world'); }); + + $app->view(function (\stdClass $view) { + return array('msg' => 'Hello '.$view->name); + }); + + $app->view(function (array $view) { + return $view['msg']; + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenersAreIgnoredIfNotSuitable() + { + $app = new Application(); + $app->get('/foo', function () { return 'Hello world'; }); + + $app->view(function (\stdClass $view) { + throw new \Exception('View listener was called'); + }); + + $app->view(function (array $view) { + throw new \Exception('View listener was called'); + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello world', $response->getContent()); + } + + public function testViewListenersResponsesAreNotUsedIfNull() + { + $app = new Application(); + $app->get('/foo', function () { return 'Hello world'; }); + + $app->view(function ($view) { + return 'Hello view listener'; + }); + + $app->view(function ($view) { + return; + }); + + $response = $app->handle(Request::create('/foo')); + + $this->assertEquals('Hello view listener', $response->getContent()); + } + + public function testWebLinkListener() + { + $app = new Application(); + + $app->get('/', function () { + return 'hello'; + }); + + $request = Request::create('/'); + $request->attributes->set('_links', (new GenericLinkProvider())->withLink(new Link('preload', '/foo.css'))); + + $response = $app->handle($request); + + $this->assertEquals('; rel="preload"', $response->headers->get('Link')); + } + + public function testDefaultRoutesFactory() + { + $app = new Application(); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $app['routes']); + } + + public function testOverriddenRoutesFactory() + { + $app = new Application(); + $app['routes_factory'] = $app->factory(function () { + return new RouteCollectionSubClass(); + }); + $this->assertInstanceOf('Silex\Tests\RouteCollectionSubClass', $app['routes']); + } +} + +class FooController +{ + public function barAction(Application $app, $name) + { + return 'Hello '.$app->escape($name); + } + + public function barSpecialAction(SpecialApplication $app, $name) + { + return 'Hello '.$app->escape($name).' in '.get_class($app); + } +} + +class IncorrectControllerCollection implements ControllerProviderInterface +{ + public function connect(Application $app) + { + return; + } +} + +class RouteCollectionSubClass extends RouteCollection +{ +} + +class SpecialApplication extends Application +{ +} diff --git a/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php b/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php new file mode 100644 index 00000000..80503b97 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.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 Silex\Tests; + +use Pimple\Container; +use Silex\CallbackResolver; + +class CallbackResolverTest extends \PHPUnit_Framework_Testcase +{ + private $app; + private $resolver; + + public function setup() + { + $this->app = new Container(); + $this->resolver = new CallbackResolver($this->app); + } + + public function testShouldResolveCallback() + { + $callable = function () {}; + $this->app['some_service'] = function () { return new \ArrayObject(); }; + $this->app['callable_service'] = function () use ($callable) { + return $callable; + }; + + $this->assertTrue($this->resolver->isValid('some_service:methodName')); + $this->assertTrue($this->resolver->isValid('callable_service')); + $this->assertEquals( + array($this->app['some_service'], 'append'), + $this->resolver->convertCallback('some_service:append') + ); + $this->assertSame($callable, $this->resolver->convertCallback('callable_service')); + } + + /** + * @dataProvider nonStringsAreNotValidProvider + */ + public function testNonStringsAreNotValid($name) + { + $this->assertFalse($this->resolver->isValid($name)); + } + + public function nonStringsAreNotValidProvider() + { + return array( + array(null), + array('some_service::methodName'), + array('missing_service'), + ); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessageRegExp /Service "[a-z_]+" is not callable./ + * @dataProvider shouldThrowAnExceptionIfServiceIsNotCallableProvider + */ + public function testShouldThrowAnExceptionIfServiceIsNotCallable($name) + { + $this->app['non_callable_obj'] = function () { return new \stdClass(); }; + $this->app['non_callable'] = function () { return array(); }; + $this->resolver->convertCallback($name); + } + + public function shouldThrowAnExceptionIfServiceIsNotCallableProvider() + { + return array( + array('non_callable_obj:methodA'), + array('non_callable'), + ); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php b/vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php new file mode 100644 index 00000000..fe96317e --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/CallbackServicesTest.php @@ -0,0 +1,109 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Silex\Provider\ServiceControllerServiceProvider; + +/** + * Callback as services test cases. + * + * @author Fabien Potencier + */ +class CallbackServicesTest extends \PHPUnit_Framework_TestCase +{ + public $called = array(); + + public function testCallbacksAsServices() + { + $app = new Application(); + $app->register(new ServiceControllerServiceProvider()); + + $app['service'] = function () { + return new CallbackServicesTest(); + }; + + $app->before('service:beforeApp'); + $app->after('service:afterApp'); + $app->finish('service:finishApp'); + $app->error('service:error'); + $app->on('kernel.request', 'service:onRequest'); + + $app + ->match('/', 'service:controller') + ->convert('foo', 'service:convert') + ->before('service:before') + ->after('service:after') + ; + + $request = Request::create('/'); + $response = $app->handle($request); + $app->terminate($request, $response); + + $this->assertEquals(array( + 'BEFORE APP', + 'ON REQUEST', + 'BEFORE', + 'CONVERT', + 'ERROR', + 'AFTER', + 'AFTER APP', + 'FINISH APP', + ), $app['service']->called); + } + + public function controller(Application $app) + { + $app->abort(404); + } + + public function before() + { + $this->called[] = 'BEFORE'; + } + + public function after() + { + $this->called[] = 'AFTER'; + } + + public function beforeApp() + { + $this->called[] = 'BEFORE APP'; + } + + public function afterApp() + { + $this->called[] = 'AFTER APP'; + } + + public function finishApp() + { + $this->called[] = 'FINISH APP'; + } + + public function error() + { + $this->called[] = 'ERROR'; + } + + public function convert() + { + $this->called[] = 'CONVERT'; + } + + public function onRequest() + { + $this->called[] = 'ON REQUEST'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php b/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php new file mode 100644 index 00000000..d5c9889c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php @@ -0,0 +1,327 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\Controller; +use Silex\ControllerCollection; +use Silex\Exception\ControllerFrozenException; +use Silex\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * ControllerCollection test cases. + * + * @author Igor Wiedler + */ +class ControllerCollectionTest extends \PHPUnit_Framework_TestCase +{ + public function testGetRouteCollectionWithNoRoutes() + { + $controllers = new ControllerCollection(new Route()); + $routes = $controllers->flush(); + $this->assertEquals(0, count($routes->all())); + } + + public function testGetRouteCollectionWithRoutes() + { + $controllers = new ControllerCollection(new Route()); + $controllers->match('/foo', function () {}); + $controllers->match('/bar', function () {}); + + $routes = $controllers->flush(); + $this->assertEquals(2, count($routes->all())); + } + + public function testControllerFreezing() + { + $controllers = new ControllerCollection(new Route()); + + $fooController = $controllers->match('/foo', function () {})->bind('foo'); + $barController = $controllers->match('/bar', function () {})->bind('bar'); + + $controllers->flush(); + + try { + $fooController->bind('foo2'); + $this->fail(); + } catch (ControllerFrozenException $e) { + } + + try { + $barController->bind('bar2'); + $this->fail(); + } catch (ControllerFrozenException $e) { + } + } + + public function testConflictingRouteNames() + { + $controllers = new ControllerCollection(new Route()); + + $mountedRootController = $controllers->match('/', function () {}); + + $mainRootController = new Controller(new Route('/')); + $mainRootController->bind($mainRootController->generateRouteName('main_1')); + + $controllers->flush(); + + $this->assertNotEquals($mainRootController->getRouteName(), $mountedRootController->getRouteName()); + } + + public function testUniqueGeneratedRouteNames() + { + $controllers = new ControllerCollection(new Route()); + + $controllers->match('/a-a', function () {}); + $controllers->match('/a_a', function () {}); + $controllers->match('/a/a', function () {}); + + $routes = $controllers->flush(); + + $this->assertCount(3, $routes->all()); + $this->assertEquals(array('_a_a', '_a_a_1', '_a_a_2'), array_keys($routes->all())); + } + + public function testUniqueGeneratedRouteNamesAmongMounts() + { + $controllers = new ControllerCollection(new Route()); + + $controllers->mount('/root-a', $rootA = new ControllerCollection(new Route())); + $controllers->mount('/root_a', $rootB = new ControllerCollection(new Route())); + + $rootA->match('/leaf', function () {}); + $rootB->match('/leaf', function () {}); + + $routes = $controllers->flush(); + + $this->assertCount(2, $routes->all()); + $this->assertEquals(array('_root_a_leaf', '_root_a_leaf_1'), array_keys($routes->all())); + } + + public function testUniqueGeneratedRouteNamesAmongNestedMounts() + { + $controllers = new ControllerCollection(new Route()); + + $controllers->mount('/root-a', $rootA = new ControllerCollection(new Route())); + $controllers->mount('/root_a', $rootB = new ControllerCollection(new Route())); + + $rootA->mount('/tree', $treeA = new ControllerCollection(new Route())); + $rootB->mount('/tree', $treeB = new ControllerCollection(new Route())); + + $treeA->match('/leaf', function () {}); + $treeB->match('/leaf', function () {}); + + $routes = $controllers->flush(); + + $this->assertCount(2, $routes->all()); + $this->assertEquals(array('_root_a_tree_leaf', '_root_a_tree_leaf_1'), array_keys($routes->all())); + } + + public function testMountCallable() + { + $controllers = new ControllerCollection(new Route()); + $controllers->mount('/prefix', function (ControllerCollection $coll) { + $coll->mount('/path', function ($coll) { + $coll->get('/part'); + }); + }); + + $routes = $controllers->flush(); + $this->assertEquals('/prefix/path/part', current($routes->all())->getPath()); + } + + public function testMountCallableProperClone() + { + $controllers = new ControllerCollection(new Route(), new RouteCollection()); + $controllers->get('/'); + + $subControllers = null; + $controllers->mount('/prefix', function (ControllerCollection $coll) use (&$subControllers) { + $subControllers = $coll; + $coll->get('/'); + }); + + $routes = $controllers->flush(); + $subRoutes = $subControllers->flush(); + $this->assertTrue($routes->count() == 2 && $subRoutes->count() == 0); + } + + public function testMountControllersFactory() + { + $testControllers = new ControllerCollection(new Route()); + $controllers = new ControllerCollection(new Route(), null, function () use ($testControllers) { + return $testControllers; + }); + + $controllers->mount('/prefix', function ($mounted) use ($testControllers) { + $this->assertSame($mounted, $testControllers); + }); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" instance or callable. + */ + public function testMountCallableException() + { + $controllers = new ControllerCollection(new Route()); + $controllers->mount('/prefix', ''); + } + + public function testAssert() + { + $controllers = new ControllerCollection(new Route()); + $controllers->assert('id', '\d+'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->assert('name', '\w+')->assert('extra', '.*'); + $controllers->assert('extra', '\w+'); + + $this->assertEquals('\d+', $controller->getRoute()->getRequirement('id')); + $this->assertEquals('\w+', $controller->getRoute()->getRequirement('name')); + $this->assertEquals('\w+', $controller->getRoute()->getRequirement('extra')); + } + + public function testValue() + { + $controllers = new ControllerCollection(new Route()); + $controllers->value('id', '1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->value('name', 'Fabien')->value('extra', 'Symfony'); + $controllers->value('extra', 'Twig'); + + $this->assertEquals('1', $controller->getRoute()->getDefault('id')); + $this->assertEquals('Fabien', $controller->getRoute()->getDefault('name')); + $this->assertEquals('Twig', $controller->getRoute()->getDefault('extra')); + } + + public function testConvert() + { + $controllers = new ControllerCollection(new Route()); + $controllers->convert('id', '1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->convert('name', 'Fabien')->convert('extra', 'Symfony'); + $controllers->convert('extra', 'Twig'); + + $this->assertEquals(array('id' => '1', 'name' => 'Fabien', 'extra' => 'Twig'), $controller->getRoute()->getOption('_converters')); + } + + public function testRequireHttp() + { + $controllers = new ControllerCollection(new Route()); + $controllers->requireHttp(); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->requireHttps(); + + $this->assertEquals(array('https'), $controller->getRoute()->getSchemes()); + + $controllers->requireHttp(); + + $this->assertEquals(array('http'), $controller->getRoute()->getSchemes()); + } + + public function testBefore() + { + $controllers = new ControllerCollection(new Route()); + $controllers->before('mid1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->before('mid2'); + $controllers->before('mid3'); + + $this->assertEquals(array('mid1', 'mid2', 'mid3'), $controller->getRoute()->getOption('_before_middlewares')); + } + + public function testAfter() + { + $controllers = new ControllerCollection(new Route()); + $controllers->after('mid1'); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->after('mid2'); + $controllers->after('mid3'); + + $this->assertEquals(array('mid1', 'mid2', 'mid3'), $controller->getRoute()->getOption('_after_middlewares')); + } + + public function testWhen() + { + $controllers = new ControllerCollection(new Route()); + $controller = $controllers->match('/{id}/{name}/{extra}', function () {})->when('request.isSecure() == true'); + + $this->assertEquals('request.isSecure() == true', $controller->getRoute()->getCondition()); + } + + public function testRouteExtension() + { + $route = new MyRoute1(); + + $controller = new ControllerCollection($route); + $controller->foo('foo'); + + $this->assertEquals('foo', $route->foo); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRouteMethodDoesNotExist() + { + $route = new MyRoute1(); + + $controller = new ControllerCollection($route); + $controller->bar(); + } + + public function testNestedCollectionRouteCallbacks() + { + $cl1 = new ControllerCollection(new MyRoute1()); + $cl2 = new ControllerCollection(new MyRoute1()); + + $c1 = $cl2->match('/c1', function () {}); + $cl1->mount('/foo', $cl2); + $c2 = $cl2->match('/c2', function () {}); + $cl1->before('before'); + $c3 = $cl2->match('/c3', function () {}); + + $cl1->flush(); + + $this->assertEquals(array('before'), $c1->getRoute()->getOption('_before_middlewares')); + $this->assertEquals(array('before'), $c2->getRoute()->getOption('_before_middlewares')); + $this->assertEquals(array('before'), $c3->getRoute()->getOption('_before_middlewares')); + } + + public function testRoutesFactoryOmitted() + { + $controllers = new ControllerCollection(new Route()); + $routes = $controllers->flush(); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); + } + + public function testRoutesFactoryInConstructor() + { + $app = new Application(); + $app['routes_factory'] = $app->factory(function () { + return new RouteCollectionSubClass2(); + }); + + $controllers = new ControllerCollection(new Route(), $app['routes_factory']); + $routes = $controllers->flush(); + $this->assertInstanceOf('Silex\Tests\RouteCollectionSubClass2', $routes); + } +} + +class MyRoute1 extends Route +{ + public $foo; + + public function foo($value) + { + $this->foo = $value; + } +} + +class RouteCollectionSubClass2 extends RouteCollection +{ +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php b/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php new file mode 100644 index 00000000..e4483886 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.php @@ -0,0 +1,38 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\ControllerResolver; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * ControllerResolver test cases. + * + * @author Fabien Potencier + */ +class ControllerResolverTest extends \PHPUnit_Framework_TestCase +{ + /** + * @group legacy + */ + public function testGetArguments() + { + $app = new Application(); + $resolver = new ControllerResolver($app); + + $controller = function (Application $app) {}; + + $args = $resolver->getArguments(Request::create('/'), $controller); + $this->assertSame($app, $args[0]); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ControllerTest.php b/vendor/silex/silex/tests/Silex/Tests/ControllerTest.php new file mode 100644 index 00000000..791563f4 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerTest.php @@ -0,0 +1,132 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Controller; +use Silex\Route; + +/** + * Controller test cases. + * + * @author Igor Wiedler + */ +class ControllerTest extends \PHPUnit_Framework_TestCase +{ + public function testBind() + { + $controller = new Controller(new Route('/foo')); + $ret = $controller->bind('foo'); + + $this->assertSame($ret, $controller); + $this->assertEquals('foo', $controller->getRouteName()); + } + + /** + * @expectedException \Silex\Exception\ControllerFrozenException + */ + public function testBindOnFrozenControllerShouldThrowException() + { + $controller = new Controller(new Route('/foo')); + $controller->bind('foo'); + $controller->freeze(); + $controller->bind('bar'); + } + + public function testAssert() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->assert('bar', '\d+'); + + $this->assertSame($ret, $controller); + $this->assertEquals(array('bar' => '\d+'), $controller->getRoute()->getRequirements()); + } + + public function testValue() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->value('bar', 'foo'); + + $this->assertSame($ret, $controller); + $this->assertEquals(array('bar' => 'foo'), $controller->getRoute()->getDefaults()); + } + + public function testConvert() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->convert('bar', $func = function ($bar) { return $bar; }); + + $this->assertSame($ret, $controller); + $this->assertEquals(array('bar' => $func), $controller->getRoute()->getOption('_converters')); + } + + public function testRun() + { + $controller = new Controller(new Route('/foo/{bar}')); + $ret = $controller->run($cb = function () { return 'foo'; }); + + $this->assertSame($ret, $controller); + $this->assertEquals($cb, $controller->getRoute()->getDefault('_controller')); + } + + /** + * @dataProvider provideRouteAndExpectedRouteName + */ + public function testDefaultRouteNameGeneration(Route $route, $prefix, $expectedRouteName) + { + $controller = new Controller($route); + $controller->bind($controller->generateRouteName($prefix)); + + $this->assertEquals($expectedRouteName, $controller->getRouteName()); + } + + public function provideRouteAndExpectedRouteName() + { + return array( + array(new Route('/Invalid%Symbols#Stripped', array(), array(), array(), '', array(), array('POST')), '', 'POST_InvalidSymbolsStripped'), + array(new Route('/post/{id}', array(), array(), array(), '', array(), array('GET')), '', 'GET_post_id'), + array(new Route('/colon:pipe|dashes-escaped'), '', '_colon_pipe_dashes_escaped'), + array(new Route('/underscores_and.periods'), '', '_underscores_and.periods'), + array(new Route('/post/{id}', array(), array(), array(), '', array(), array('GET')), 'prefix', 'GET_prefix_post_id'), + ); + } + + public function testRouteExtension() + { + $route = new MyRoute(); + + $controller = new Controller($route); + $controller->foo('foo'); + + $this->assertEquals('foo', $route->foo); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRouteMethodDoesNotExist() + { + $route = new MyRoute(); + + $controller = new Controller($route); + $controller->bar(); + } +} + +class MyRoute extends Route +{ + public $foo; + + public function foo($value) + { + $this->foo = $value; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php b/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php new file mode 100644 index 00000000..1937ab32 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php @@ -0,0 +1,93 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\EventListener; + +use Psr\Log\LogLevel; +use Silex\EventListener\LogListener; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Exception\HttpException; + +/** + * LogListener. + * + * @author Jérôme Tamarelle + */ +class LogListenerTest extends \PHPUnit_Framework_TestCase +{ + public function testRequestListener() + { + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->with(LogLevel::DEBUG, '> GET /foo') + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new LogListener($logger)); + + $kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface')->getMock(); + + $dispatcher->dispatch(KernelEvents::REQUEST, new GetResponseEvent($kernel, Request::create('/subrequest'), HttpKernelInterface::SUB_REQUEST), 'Skip sub requests'); + + $dispatcher->dispatch(KernelEvents::REQUEST, new GetResponseEvent($kernel, Request::create('/foo'), HttpKernelInterface::MASTER_REQUEST), 'Log master requests'); + } + + public function testResponseListener() + { + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->with(LogLevel::DEBUG, '< 301') + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new LogListener($logger)); + + $kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface')->getMock(); + + $dispatcher->dispatch(KernelEvents::RESPONSE, new FilterResponseEvent($kernel, Request::create('/foo'), HttpKernelInterface::SUB_REQUEST, Response::create('subrequest', 200)), 'Skip sub requests'); + + $dispatcher->dispatch(KernelEvents::RESPONSE, new FilterResponseEvent($kernel, Request::create('/foo'), HttpKernelInterface::MASTER_REQUEST, Response::create('bar', 301)), 'Log master requests'); + } + + public function testExceptionListener() + { + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); + $logger + ->expects($this->at(0)) + ->method('log') + ->with(LogLevel::CRITICAL, 'RuntimeException: Fatal error (uncaught exception) at '.__FILE__.' line '.(__LINE__ + 13)) + ; + $logger + ->expects($this->at(1)) + ->method('log') + ->with(LogLevel::ERROR, 'Symfony\Component\HttpKernel\Exception\HttpException: Http error (uncaught exception) at '.__FILE__.' line '.(__LINE__ + 9)) + ; + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new LogListener($logger)); + + $kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface')->getMock(); + + $dispatcher->dispatch(KernelEvents::EXCEPTION, new GetResponseForExceptionEvent($kernel, Request::create('/foo'), HttpKernelInterface::SUB_REQUEST, new \RuntimeException('Fatal error'))); + $dispatcher->dispatch(KernelEvents::EXCEPTION, new GetResponseForExceptionEvent($kernel, Request::create('/foo'), HttpKernelInterface::SUB_REQUEST, new HttpException(400, 'Http error'))); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php b/vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php new file mode 100644 index 00000000..24e9a0dd --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ExceptionHandlerTest.php @@ -0,0 +1,406 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * Error handler test cases. + * + * @author Igor Wiedler + */ +class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testExceptionHandlerExceptionNoDebug() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('

Whoops, looks like something went wrong.

', $response->getContent()); + $this->assertEquals(500, $response->getStatusCode()); + } + + public function testExceptionHandlerExceptionDebug() + { + $app = new Application(); + $app['debug'] = true; + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + + $this->assertContains('foo exception', $response->getContent()); + $this->assertEquals(500, $response->getStatusCode()); + } + + public function testExceptionHandlerNotFoundNoDebug() + { + $app = new Application(); + $app['debug'] = false; + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('

Sorry, the page you are looking for could not be found.

', $response->getContent()); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testExceptionHandlerNotFoundDebug() + { + $app = new Application(); + $app['debug'] = true; + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('No route found for "GET /foo"', html_entity_decode($response->getContent())); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testExceptionHandlerMethodNotAllowedNoDebug() + { + $app = new Application(); + $app['debug'] = false; + + $app->get('/foo', function () { return 'foo'; }); + + $request = Request::create('/foo', 'POST'); + $response = $app->handle($request); + $this->assertContains('

Whoops, looks like something went wrong.

', $response->getContent()); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('GET', $response->headers->get('Allow')); + } + + public function testExceptionHandlerMethodNotAllowedDebug() + { + $app = new Application(); + $app['debug'] = true; + + $app->get('/foo', function () { return 'foo'; }); + + $request = Request::create('/foo', 'POST'); + $response = $app->handle($request); + $this->assertContains('No route found for "POST /foo": Method Not Allowed (Allow: GET)', html_entity_decode($response->getContent())); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('GET', $response->headers->get('Allow')); + } + + public function testNoExceptionHandler() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('->handle() should not catch exceptions where no error handler was supplied'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception', $e->getMessage()); + } + } + + public function testOneExceptionHandler() + { + $app = new Application(); + + $app->match('/500', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->match('/404', function () { + throw new NotFoundHttpException('foo exception'); + }); + + $app->get('/405', function () { return 'foo'; }); + + $app->error(function ($e, $code) { + return new Response('foo exception handler'); + }); + + $response = $this->checkRouteResponse($app, '/500', 'foo exception handler'); + $this->assertEquals(500, $response->getStatusCode()); + + $response = $app->handle(Request::create('/404')); + $this->assertEquals(404, $response->getStatusCode()); + + $response = $app->handle(Request::create('/405', 'POST')); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('GET', $response->headers->get('Allow')); + } + + public function testMultipleExceptionHandlers() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $errors = 0; + + $app->error(function ($e) use (&$errors) { + ++$errors; + }); + + $app->error(function ($e) use (&$errors) { + ++$errors; + }); + + $app->error(function ($e) use (&$errors) { + ++$errors; + + return new Response('foo exception handler'); + }); + + $app->error(function ($e) use (&$errors) { + // should not execute + ++$errors; + }); + + $request = Request::create('/foo'); + $this->checkRouteResponse($app, '/foo', 'foo exception handler', 'should return the first response returned by an exception handler'); + + $this->assertEquals(3, $errors, 'should execute error handlers until a response is returned'); + } + + public function testNoResponseExceptionHandler() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $errors = 0; + + $app->error(function ($e) use (&$errors) { + ++$errors; + }); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('->handle() should not catch exceptions where an empty error handler was supplied'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception', $e->getMessage()); + } catch (\LogicException $e) { + $this->assertEquals('foo exception', $e->getPrevious()->getMessage()); + } + + $this->assertEquals(1, $errors, 'should execute the error handler'); + } + + public function testStringResponseExceptionHandler() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->error(function ($e) { + return 'foo exception handler'; + }); + + $request = Request::create('/foo'); + $this->checkRouteResponse($app, '/foo', 'foo exception handler', 'should accept a string response from the error handler'); + } + + public function testExceptionHandlerException() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->error(function ($e) { + throw new \RuntimeException('foo exception handler exception'); + }); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('->handle() should not catch exceptions thrown from an error handler'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception handler exception', $e->getMessage()); + } + } + + public function testRemoveExceptionHandlerAfterDispatcherAccess() + { + $app = new Application(); + + $app->match('/foo', function () { + throw new \RuntimeException('foo exception'); + }); + + $app->before(function () { + // just making sure the dispatcher gets created + }); + + unset($app['exception_handler']); + + try { + $request = Request::create('/foo'); + $app->handle($request); + $this->fail('default exception handler should have been removed'); + } catch (\RuntimeException $e) { + $this->assertEquals('foo exception', $e->getMessage()); + } + } + + public function testExceptionHandlerWithDefaultException() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + throw new \Exception(); + }); + + $app->error(function (\Exception $e) { + return new Response('Exception thrown', 500); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Exception thrown', $response->getContent()); + $this->assertEquals(500, $response->getStatusCode()); + } + + public function testExceptionHandlerWithStandardException() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + // Throw a normal exception + throw new \Exception(); + }); + + // Register 2 error handlers, each with a specified Exception class + // Since we throw a standard Exception above only + // the second error handler should fire + $app->error(function (\LogicException $e) { // Extends \Exception + + return 'Caught LogicException'; + }); + $app->error(function (\Exception $e) { + return 'Caught Exception'; + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught Exception', $response->getContent()); + } + + public function testExceptionHandlerWithSpecifiedException() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + // Throw a specified exception + throw new \LogicException(); + }); + + // Register 2 error handlers, each with a specified Exception class + // Since we throw a LogicException above + // the first error handler should fire + $app->error(function (\LogicException $e) { // Extends \Exception + + return 'Caught LogicException'; + }); + $app->error(function (\Exception $e) { + return 'Caught Exception'; + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught LogicException', $response->getContent()); + } + + public function testExceptionHandlerWithSpecifiedExceptionInReverseOrder() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + // Throw a specified exception + throw new \LogicException(); + }); + + // Register the \Exception error handler first, since the + // error handler works with an instanceof mechanism the + // second more specific error handler should not fire since + // the \Exception error handler is registered first and also + // captures all exceptions that extend it + $app->error(function (\Exception $e) { + return 'Caught Exception'; + }); + $app->error(function (\LogicException $e) { // Extends \Exception + + return 'Caught LogicException'; + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught Exception', $response->getContent()); + } + + public function testExceptionHandlerWithArrayStyleCallback() + { + $app = new Application(); + $app['debug'] = false; + + $app->match('/foo', function () { + throw new \Exception(); + }); + + // Array style callback for error handler + $app->error(array($this, 'exceptionHandler')); + + $request = Request::create('/foo'); + $response = $app->handle($request); + $this->assertContains('Caught Exception', $response->getContent()); + } + + protected function checkRouteResponse($app, $path, $expectedContent, $method = 'get', $message = null) + { + $request = Request::create($path, $method); + $response = $app->handle($request); + $this->assertEquals($expectedContent, $response->getContent(), $message); + + return $response; + } + + public function exceptionHandler() + { + return 'Caught Exception'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php b/vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php new file mode 100644 index 00000000..c16f14ed --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Fixtures/Php7Controller.php @@ -0,0 +1,22 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Fixtures; + +use Silex\Application; + +class Php7Controller +{ + public function typehintedAction(Application $application, string $name) + { + return 'Hello '.$application->escape($name).' in '.get_class($application); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php b/vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php new file mode 100644 index 00000000..f2af4ac3 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/FunctionalTest.php @@ -0,0 +1,58 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\Route; +use Silex\ControllerCollection; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Functional test cases. + * + * @author Igor Wiedler + */ +class FunctionalTest extends \PHPUnit_Framework_TestCase +{ + public function testBind() + { + $app = new Application(); + + $app->get('/', function () { + return 'hello'; + }) + ->bind('homepage'); + + $app->get('/foo', function () { + return 'foo'; + }) + ->bind('foo_abc'); + + $app->flush(); + $routes = $app['routes']; + $this->assertInstanceOf('Symfony\Component\Routing\Route', $routes->get('homepage')); + $this->assertInstanceOf('Symfony\Component\Routing\Route', $routes->get('foo_abc')); + } + + public function testMount() + { + $mounted = new ControllerCollection(new Route()); + $mounted->get('/{name}', function ($name) { return new Response($name); }); + + $app = new Application(); + $app->mount('/hello', $mounted); + + $response = $app->handle(Request::create('/hello/Silex')); + $this->assertEquals('Silex', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/JsonTest.php b/vendor/silex/silex/tests/Silex/Tests/JsonTest.php new file mode 100644 index 00000000..5eb13362 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/JsonTest.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; + +/** + * JSON test cases. + * + * @author Igor Wiedler + */ +class JsonTest extends \PHPUnit_Framework_TestCase +{ + public function testJsonReturnsJsonResponse() + { + $app = new Application(); + + $response = $app->json(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $response = json_decode($response->getContent(), true); + $this->assertSame(array(), $response); + } + + public function testJsonUsesData() + { + $app = new Application(); + + $response = $app->json(array('foo' => 'bar')); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + public function testJsonUsesStatus() + { + $app = new Application(); + + $response = $app->json(array(), 202); + $this->assertSame(202, $response->getStatusCode()); + } + + public function testJsonUsesHeaders() + { + $app = new Application(); + + $response = $app->json(array(), 200, array('ETag' => 'foo')); + $this->assertSame('foo', $response->headers->get('ETag')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php b/vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php new file mode 100644 index 00000000..1b4c580c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LazyDispatcherTest.php @@ -0,0 +1,59 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +class LazyDispatcherTest extends \PHPUnit_Framework_TestCase +{ + /** @test */ + public function beforeMiddlewareShouldNotCreateDispatcherEarly() + { + $dispatcherCreated = false; + + $app = new Application(); + $app->extend('dispatcher', function ($dispatcher, $app) use (&$dispatcherCreated) { + $dispatcherCreated = true; + + return $dispatcher; + }); + + $app->before(function () {}); + + $this->assertFalse($dispatcherCreated); + + $request = Request::create('/'); + $app->handle($request); + + $this->assertTrue($dispatcherCreated); + } + + /** @test */ + public function eventHelpersShouldDirectlyAddListenersAfterBoot() + { + $app = new Application(); + + $fired = false; + $app->get('/', function () use ($app, &$fired) { + $app->finish(function () use (&$fired) { + $fired = true; + }); + }); + + $request = Request::create('/'); + $response = $app->handle($request); + $app->terminate($request, $response); + + $this->assertTrue($fired, 'Event was not fired'); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php b/vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php new file mode 100644 index 00000000..7ecb5b2f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LazyRequestMatcherTest.php @@ -0,0 +1,77 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Symfony\Component\HttpFoundation\Request; +use Silex\Provider\Routing\LazyRequestMatcher; + +/** + * LazyRequestMatcher test case. + * + * @author Leszek Prabucki + */ +class LazyRequestMatcherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Silex\LazyRequestMatcher::getRequestMatcher + */ + public function testUserMatcherIsCreatedLazily() + { + $callCounter = 0; + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + + $matcher = new LazyRequestMatcher(function () use ($requestMatcher, &$callCounter) { + ++$callCounter; + + return $requestMatcher; + }); + + $this->assertEquals(0, $callCounter); + $request = Request::create('path'); + $matcher->matchRequest($request); + $this->assertEquals(1, $callCounter); + } + + /** + * @expectedException LogicException + * @expectedExceptionMessage Factory supplied to LazyRequestMatcher must return implementation of Symfony\Component\Routing\RequestMatcherInterface. + */ + public function testThatCanInjectRequestMatcherOnly() + { + $matcher = new LazyRequestMatcher(function () { + return 'someMatcher'; + }); + + $request = Request::create('path'); + $matcher->matchRequest($request); + } + + /** + * @covers Silex\LazyRequestMatcher::matchRequest + */ + public function testMatchIsProxy() + { + $request = Request::create('path'); + $matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $matcher->expects($this->once()) + ->method('matchRequest') + ->with($request) + ->will($this->returnValue('matcherReturnValue')); + + $matcher = new LazyRequestMatcher(function () use ($matcher) { + return $matcher; + }); + $result = $matcher->matchRequest($request); + + $this->assertEquals('matcherReturnValue', $result); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php b/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php new file mode 100644 index 00000000..ada57be4 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php @@ -0,0 +1,83 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\Provider\LocaleServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Locale test cases. + * + * @author Fabien Potencier + */ +class LocaleTest extends \PHPUnit_Framework_TestCase +{ + public function testLocale() + { + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('en', $response->getContent()); + + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app['locale'] = 'fr'; + $app->get('/', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('fr', $response->getContent()); + + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/{_locale}', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/es')); + $this->assertEquals('es', $response->getContent()); + } + + public function testLocaleInSubRequests() + { + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/embed/{_locale}', function (Request $request) { return $request->getLocale(); }); + $app->get('/{_locale}', function (Request $request) use ($app) { + return $request->getLocale().$app->handle(Request::create('/embed/es'), HttpKernelInterface::SUB_REQUEST)->getContent().$request->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + $this->assertEquals('fresfr', $response->getContent()); + + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->get('/embed', function (Request $request) { return $request->getLocale(); }); + $app->get('/{_locale}', function (Request $request) use ($app) { + return $request->getLocale().$app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent().$request->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + // locale in sub-request must be "en" as this is the value if the sub-request is converted to an ESI + $this->assertEquals('frenfr', $response->getContent()); + } + + public function testLocaleWithBefore() + { + $app = new Application(); + $app->register(new LocaleServiceProvider()); + $app->before(function (Request $request) use ($app) { $request->setLocale('fr'); }); + $app->get('/embed', function (Request $request) { return $request->getLocale(); }); + $app->get('/', function (Request $request) use ($app) { + return $request->getLocale().$app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent().$request->getLocale(); + }); + $response = $app->handle(Request::create('/')); + // locale in sub-request is "en" as the before filter is only executed for the main request + $this->assertEquals('frenfr', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php b/vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php new file mode 100644 index 00000000..376a42c6 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/MiddlewareTest.php @@ -0,0 +1,307 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Middleware test cases. + * + * @author Igor Wiedler + */ +class MiddlewareTest extends \PHPUnit_Framework_TestCase +{ + public function testBeforeAndAfterFilter() + { + $i = 0; + $test = $this; + + $app = new Application(); + + $app->before(function () use (&$i, $test) { + $test->assertEquals(0, $i); + ++$i; + }); + + $app->match('/foo', function () use (&$i, $test) { + $test->assertEquals(1, $i); + ++$i; + }); + + $app->after(function () use (&$i, $test) { + $test->assertEquals(2, $i); + ++$i; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(3, $i); + } + + public function testAfterFilterWithResponseObject() + { + $i = 0; + + $app = new Application(); + + $app->match('/foo', function () use (&$i) { + ++$i; + + return new Response('foo'); + }); + + $app->after(function () use (&$i) { + ++$i; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(2, $i); + } + + public function testMultipleFilters() + { + $i = 0; + $test = $this; + + $app = new Application(); + + $app->before(function () use (&$i, $test) { + $test->assertEquals(0, $i); + ++$i; + }); + + $app->before(function () use (&$i, $test) { + $test->assertEquals(1, $i); + ++$i; + }); + + $app->match('/foo', function () use (&$i, $test) { + $test->assertEquals(2, $i); + ++$i; + }); + + $app->after(function () use (&$i, $test) { + $test->assertEquals(3, $i); + ++$i; + }); + + $app->after(function () use (&$i, $test) { + $test->assertEquals(4, $i); + ++$i; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(5, $i); + } + + public function testFiltersShouldFireOnException() + { + $i = 0; + + $app = new Application(); + + $app->before(function () use (&$i) { + ++$i; + }); + + $app->match('/foo', function () { + throw new \RuntimeException(); + }); + + $app->after(function () use (&$i) { + ++$i; + }); + + $app->error(function () { + return 'error handled'; + }); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertEquals(2, $i); + } + + public function testFiltersShouldFireOnHttpException() + { + $i = 0; + + $app = new Application(); + + $app->before(function () use (&$i) { + ++$i; + }, Application::EARLY_EVENT); + + $app->after(function () use (&$i) { + ++$i; + }); + + $app->error(function () { + return 'error handled'; + }); + + $request = Request::create('/nowhere'); + $app->handle($request); + + $this->assertEquals(2, $i); + } + + public function testBeforeFilterPreventsBeforeMiddlewaresToBeExecuted() + { + $app = new Application(); + + $app->before(function () { return new Response('app before'); }); + + $app->get('/', function () { + return new Response('test'); + })->before(function () { + return new Response('middleware before'); + }); + + $this->assertEquals('app before', $app->handle(Request::create('/'))->getContent()); + } + + public function testBeforeFilterExceptionsWhenHandlingAnException() + { + $app = new Application(); + + $app->before(function () { throw new \RuntimeException(''); }); + + // even if the before filter throws an exception, we must have the 404 + $this->assertEquals(404, $app->handle(Request::create('/'))->getStatusCode()); + } + + public function testRequestShouldBePopulatedOnBefore() + { + $app = new Application(); + + $app->before(function (Request $request) use ($app) { + $app['project'] = $request->get('project'); + }); + + $app->match('/foo/{project}', function () use ($app) { + return $app['project']; + }); + + $request = Request::create('/foo/bar'); + $this->assertEquals('bar', $app->handle($request)->getContent()); + + $request = Request::create('/foo/baz'); + $this->assertEquals('baz', $app->handle($request)->getContent()); + } + + public function testBeforeFilterAccessesRequestAndCanReturnResponse() + { + $app = new Application(); + + $app->before(function (Request $request) { + return new Response($request->get('name')); + }); + + $app->match('/', function () use ($app) { throw new \Exception('Should never be executed'); }); + + $request = Request::create('/?name=Fabien'); + $this->assertEquals('Fabien', $app->handle($request)->getContent()); + } + + public function testAfterFilterAccessRequestResponse() + { + $app = new Application(); + + $app->after(function (Request $request, Response $response) { + $response->setContent($response->getContent().'---'); + }); + + $app->match('/', function () { return new Response('foo'); }); + + $request = Request::create('/'); + $this->assertEquals('foo---', $app->handle($request)->getContent()); + } + + public function testAfterFilterCanReturnResponse() + { + $app = new Application(); + + $app->after(function (Request $request, Response $response) { + return new Response('bar'); + }); + + $app->match('/', function () { return new Response('foo'); }); + + $request = Request::create('/'); + $this->assertEquals('bar', $app->handle($request)->getContent()); + } + + public function testRouteAndApplicationMiddlewareParameterInjection() + { + $app = new Application(); + + $test = $this; + + $middlewareTarget = array(); + $applicationBeforeMiddleware = function ($request, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'application_before_middleware_triggered'; + }; + + $applicationAfterMiddleware = function ($request, $response, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'application_after_middleware_triggered'; + }; + + $applicationFinishMiddleware = function ($request, $response, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'application_finish_middleware_triggered'; + }; + + $routeBeforeMiddleware = function ($request, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'route_before_middleware_triggered'; + }; + + $routeAfterMiddleware = function ($request, $response, $app) use (&$middlewareTarget, $test) { + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Request', $request); + $test->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $response); + $test->assertInstanceOf('\Silex\Application', $app); + $middlewareTarget[] = 'route_after_middleware_triggered'; + }; + + $app->before($applicationBeforeMiddleware); + $app->after($applicationAfterMiddleware); + $app->finish($applicationFinishMiddleware); + + $app->match('/', function () { + return new Response('foo'); + }) + ->before($routeBeforeMiddleware) + ->after($routeAfterMiddleware); + + $request = Request::create('/'); + $response = $app->handle($request); + $app->terminate($request, $response); + + $this->assertSame(array('application_before_middleware_triggered', 'route_before_middleware_triggered', 'route_after_middleware_triggered', 'application_after_middleware_triggered', 'application_finish_middleware_triggered'), $middlewareTarget); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php new file mode 100644 index 00000000..3dfb5d3f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/AssetServiceProviderTest.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\AssetServiceProvider; + +class AssetServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testGenerateAssetUrl() + { + $app = new Application(); + $app->register(new AssetServiceProvider(), array( + 'assets.version' => 'v1', + 'assets.version_format' => '%s?version=%s', + 'assets.named_packages' => array( + 'css' => array('version' => 'css2', 'base_path' => '/whatever-makes-sense'), + 'images' => array('base_urls' => array('https://img.example.com')), + ), + )); + + $this->assertEquals('/foo.png?version=v1', $app['assets.packages']->getUrl('/foo.png')); + $this->assertEquals('/whatever-makes-sense/foo.css?css2', $app['assets.packages']->getUrl('foo.css', 'css')); + $this->assertEquals('https://img.example.com/foo.png', $app['assets.packages']->getUrl('/foo.png', 'images')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php new file mode 100644 index 00000000..5a7e9a2d --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php @@ -0,0 +1,116 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Pimple\Container; +use Silex\Application; +use Silex\Provider\DoctrineServiceProvider; + +/** + * DoctrineProvider test cases. + * + * Fabien Potencier + */ +class DoctrineServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testOptionsInitializer() + { + $app = new Application(); + $app->register(new DoctrineServiceProvider()); + + $this->assertEquals($app['db.default_options'], $app['db']->getParams()); + } + + public function testSingleConnection() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Application(); + $app->register(new DoctrineServiceProvider(), array( + 'db.options' => array('driver' => 'pdo_sqlite', 'memory' => true), + )); + + $db = $app['db']; + $params = $db->getParams(); + $this->assertTrue(array_key_exists('memory', $params)); + $this->assertTrue($params['memory']); + $this->assertInstanceof('Doctrine\DBAL\Driver\PDOSqlite\Driver', $db->getDriver()); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + + $this->assertSame($app['dbs']['default'], $db); + } + + public function testMultipleConnections() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Application(); + $app->register(new DoctrineServiceProvider(), array( + 'dbs.options' => array( + 'sqlite1' => array('driver' => 'pdo_sqlite', 'memory' => true), + 'sqlite2' => array('driver' => 'pdo_sqlite', 'path' => sys_get_temp_dir().'/silex'), + ), + )); + + $db = $app['db']; + $params = $db->getParams(); + $this->assertTrue(array_key_exists('memory', $params)); + $this->assertTrue($params['memory']); + $this->assertInstanceof('Doctrine\DBAL\Driver\PDOSqlite\Driver', $db->getDriver()); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + + $this->assertSame($app['dbs']['sqlite1'], $db); + + $db2 = $app['dbs']['sqlite2']; + $params = $db2->getParams(); + $this->assertTrue(array_key_exists('path', $params)); + $this->assertEquals(sys_get_temp_dir().'/silex', $params['path']); + } + + public function testLoggerLoading() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Application(); + $this->assertTrue(isset($app['logger'])); + $this->assertNull($app['logger']); + $app->register(new DoctrineServiceProvider(), array( + 'dbs.options' => array( + 'sqlite1' => array('driver' => 'pdo_sqlite', 'memory' => true), + ), + )); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + $this->assertNull($app['db']->getConfiguration()->getSQLLogger()); + } + + public function testLoggerNotLoaded() + { + if (!in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('pdo_sqlite is not available'); + } + + $app = new Container(); + $app->register(new DoctrineServiceProvider(), array( + 'dbs.options' => array( + 'sqlite1' => array('driver' => 'pdo_sqlite', 'memory' => true), + ), + )); + $this->assertEquals(22, $app['db']->fetchColumn('SELECT 22')); + $this->assertNull($app['db']->getConfiguration()->getSQLLogger()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php new file mode 100644 index 00000000..0d32a8bf --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php @@ -0,0 +1,362 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\FormServiceProvider; +use Silex\Provider\CsrfServiceProvider; +use Silex\Provider\SessionServiceProvider; +use Silex\Provider\TranslationServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\FormTypeGuesserChain; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; +use Symfony\Component\Translation\Exception\NotFoundResourceException; + +class FormServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testFormFactoryServiceIsFormFactory() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']); + } + + public function testFormRegistryServiceIsFormRegistry() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $this->assertInstanceOf('Symfony\Component\Form\FormRegistry', $app['form.registry']); + } + + public function testFormServiceProviderWillLoadTypes() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.types', function ($extensions) { + $extensions[] = new DummyFormType(); + + return $extensions; + }); + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'Silex\Tests\Provider\DummyFormType') + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + public function testFormServiceProviderWillLoadTypesServices() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['dummy'] = function () { + return new DummyFormType(); + }; + $app->extend('form.types', function ($extensions) { + $extensions[] = 'dummy'; + + return $extensions; + }); + + $form = $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'dummy') + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid form type. The silex service "dummy" does not exist. + */ + public function testNonExistentTypeService() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.types', function ($extensions) { + $extensions[] = 'dummy'; + + return $extensions; + }); + + $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'dummy') + ->getForm(); + } + + public function testFormServiceProviderWillLoadTypeExtensions() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = new DummyFormTypeExtension(); + + return $extensions; + }); + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType', array('image_path' => 'webPath')) + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + public function testFormServiceProviderWillLoadTypeExtensionsServices() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['dummy.form.type.extension'] = function () { + return new DummyFormTypeExtension(); + }; + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = 'dummy.form.type.extension'; + + return $extensions; + }); + + $form = $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType', array('image_path' => 'webPath')) + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid form type extension. The silex service "dummy.form.type.extension" does not exist. + */ + public function testNonExistentTypeExtensionService() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = 'dummy.form.type.extension'; + + return $extensions; + }); + + $app['form.factory'] + ->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->add('dummy', 'dummy.form.type') + ->getForm(); + } + + public function testFormServiceProviderWillLoadTypeGuessers() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.guessers', function ($guessers) { + $guessers[] = new FormTypeGuesserChain(array()); + + return $guessers; + }); + + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']); + } + + public function testFormServiceProviderWillLoadTypeGuessersServices() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['dummy.form.type.guesser'] = function () { + return new FormTypeGuesserChain(array()); + }; + $app->extend('form.type.guessers', function ($guessers) { + $guessers[] = 'dummy.form.type.guesser'; + + return $guessers; + }); + + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $app['form.factory']); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid form type guesser. The silex service "dummy.form.type.guesser" does not exist. + */ + public function testNonExistentTypeGuesserService() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app->extend('form.type.guessers', function ($extensions) { + $extensions[] = 'dummy.form.type.guesser'; + + return $extensions; + }); + + $factory = $app['form.factory']; + } + + public function testFormServiceProviderWillUseTranslatorIfAvailable() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app['translator.domains'] = array( + 'messages' => array( + 'de' => array( + 'The CSRF token is invalid. Please try to resubmit the form.' => 'German translation', + ), + ), + ); + $app['locale'] = 'de'; + + $app['csrf.token_manager'] = function () { + return $this->getMockBuilder('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')->getMock(); + }; + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array()) + ->getForm(); + + $form->handleRequest($req = Request::create('/', 'POST', array('form' => array( + '_token' => 'the wrong token', + )))); + + $this->assertFalse($form->isValid()); + $r = new \ReflectionMethod($form, 'getErrors'); + if (!$r->getNumberOfParameters()) { + $this->assertContains('ERROR: German translation', $form->getErrorsAsString()); + } else { + // as of 2.5 + $this->assertContains('ERROR: German translation', (string) $form->getErrors(true, false)); + } + } + + public function testFormServiceProviderWillNotAddNonexistentTranslationFiles() + { + $app = new Application(array( + 'locale' => 'nonexistent', + )); + + $app->register(new FormServiceProvider()); + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider(), array( + 'locale_fallbacks' => array(), + )); + + $app['form.factory']; + $translator = $app['translator']; + + try { + $translator->trans('test'); + } catch (NotFoundResourceException $e) { + $this->fail('Form factory should not add a translation resource that does not exist'); + } + } + + public function testFormCsrf() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new SessionServiceProvider()); + $app->register(new CsrfServiceProvider()); + $app['session.test'] = true; + + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())->getForm(); + + $this->assertTrue(isset($form->createView()['_token'])); + } + + public function testUserExtensionCanConfigureDefaultExtensions() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new SessionServiceProvider()); + $app->register(new CsrfServiceProvider()); + $app['session.test'] = true; + + $app->extend('form.type.extensions', function ($extensions) { + $extensions[] = new FormServiceProviderTest\DisableCsrfExtension(); + + return $extensions; + }); + $form = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array())->getForm(); + + $this->assertFalse($form->getConfig()->getOption('csrf_protection')); + } +} + +if (!class_exists('Symfony\Component\Form\Deprecated\FormEvents')) { + class DummyFormType extends AbstractType + { + } +} else { + // FormTypeInterface::getName() is needed by the form component 2.8.x + class DummyFormType extends AbstractType + { + /** + * @return string The name of this type + */ + public function getName() + { + return 'dummy'; + } + } +} + +if (method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) { + class DummyFormTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'Symfony\Component\Form\Extension\Core\Type\FileType'; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefined(array('image_path')); + } + } +} else { + class DummyFormTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'Symfony\Component\Form\Extension\Core\Type\FileType'; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + if (!method_exists($resolver, 'setDefined')) { + $resolver->setOptional(array('image_path')); + } else { + $resolver->setDefined(array('image_path')); + } + } + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php new file mode 100644 index 00000000..8c82237d --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest/DisableCsrfExtension.php @@ -0,0 +1,22 @@ +setDefaults(array( + 'csrf_protection' => false, + )); + } + + public function getExtendedType() + { + return FormType::class; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php new file mode 100644 index 00000000..93da4fe9 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpCacheServiceProviderTest.php @@ -0,0 +1,80 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\HttpCacheServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * HttpCacheProvider test cases. + * + * @author Igor Wiedler + */ +class HttpCacheServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->register(new HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => sys_get_temp_dir().'/silex_http_cache_'.uniqid(), + )); + + $this->assertInstanceOf('Silex\Provider\HttpCache\HttpCache', $app['http_cache']); + + return $app; + } + + /** + * @depends testRegister + */ + public function testRunCallsShutdown($app) + { + $finished = false; + + $app->finish(function () use (&$finished) { + $finished = true; + }); + + $app->get('/', function () use ($app) { + return new UnsendableResponse('will do something after finish'); + }); + + $request = Request::create('/'); + $app['http_cache']->run($request); + + $this->assertTrue($finished); + } + + public function testDebugDefaultsToThatOfApp() + { + $app = new Application(); + + $app->register(new HttpCacheServiceProvider(), array( + 'http_cache.cache_dir' => sys_get_temp_dir().'/silex_http_cache_'.uniqid(), + )); + + $app['debug'] = true; + $app['http_cache']; + $this->assertTrue($app['http_cache.options']['debug']); + } +} + +class UnsendableResponse extends Response +{ + public function send() + { + // do nothing + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php new file mode 100644 index 00000000..1a7620c1 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php @@ -0,0 +1,48 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\HttpCacheServiceProvider; +use Silex\Provider\HttpFragmentServiceProvider; +use Silex\Provider\TwigServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +class HttpFragmentServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRenderFunction() + { + $app = new Application(); + unset($app['exception_handler']); + + $app->register(new HttpFragmentServiceProvider()); + $app->register(new HttpCacheServiceProvider(), array('http_cache.cache_dir' => sys_get_temp_dir())); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'hello' => '{{ render("/foo") }}{{ render_esi("/foo") }}{{ render_hinclude("/foo") }}', + 'foo' => 'foo', + ), + )); + + $app->get('/hello', function () use ($app) { + return $app['twig']->render('hello'); + }); + + $app->get('/foo', function () use ($app) { + return $app['twig']->render('foo'); + }); + + $response = $app['http_cache']->handle(Request::create('/hello')); + + $this->assertEquals('foofoo', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php new file mode 100644 index 00000000..9d8c260a --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php @@ -0,0 +1,256 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Handler\TestHandler; +use Monolog\Logger; +use Silex\Application; +use Silex\Provider\MonologServiceProvider; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Kernel; + +/** + * MonologProvider test cases. + * + * @author Igor Wiedler + */ +class MonologServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + private $currErrorHandler; + + protected function setUp() + { + $this->currErrorHandler = set_error_handler('var_dump'); + restore_error_handler(); + } + + protected function tearDown() + { + set_error_handler($this->currErrorHandler); + } + + public function testRequestLogging() + { + $app = $this->getApplication(); + + $app->get('/foo', function () use ($app) { + return 'foo'; + }); + + $this->assertFalse($app['monolog.handler']->hasInfoRecords()); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertTrue($app['monolog.handler']->hasDebug('> GET /foo')); + $this->assertTrue($app['monolog.handler']->hasDebug('< 200')); + + $records = $app['monolog.handler']->getRecords(); + if (Kernel::VERSION_ID < 30100) { + $this->assertContains('Matched route "GET_foo"', $records[0]['message']); + } else { + $this->assertContains('Matched route "{route}".', $records[0]['message']); + $this->assertSame('GET_foo', $records[0]['context']['route']); + } + } + + public function testManualLogging() + { + $app = $this->getApplication(); + + $app->get('/log', function () use ($app) { + $app['monolog']->addDebug('logging a message'); + }); + + $this->assertFalse($app['monolog.handler']->hasDebugRecords()); + + $request = Request::create('/log'); + $app->handle($request); + + $this->assertTrue($app['monolog.handler']->hasDebug('logging a message')); + } + + public function testOverrideFormatter() + { + $app = new Application(); + + $app->register(new MonologServiceProvider(), array( + 'monolog.formatter' => new JsonFormatter(), + 'monolog.logfile' => 'php://memory', + )); + + $this->assertInstanceOf('Monolog\Formatter\JsonFormatter', $app['monolog.handler']->getFormatter()); + } + + public function testErrorLogging() + { + $app = $this->getApplication(); + + $app->error(function (\Exception $e) { + return 'error handled'; + }); + + /* + * Simulate 404, logged to error level + */ + $this->assertFalse($app['monolog.handler']->hasErrorRecords()); + + $request = Request::create('/error'); + $app->handle($request); + + $pattern = "#Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\NotFoundHttpException: No route found for \"GET /error\" \(uncaught exception\) at .* line \d+#"; + $this->assertMatchingRecord($pattern, Logger::ERROR, $app['monolog.handler']); + + /* + * Simulate unhandled exception, logged to critical + */ + $app->get('/error', function () { + throw new \RuntimeException('very bad error'); + }); + + $this->assertFalse($app['monolog.handler']->hasCriticalRecords()); + + $request = Request::create('/error'); + $app->handle($request); + + $pattern = "#RuntimeException: very bad error \(uncaught exception\) at .* line \d+#"; + $this->assertMatchingRecord($pattern, Logger::CRITICAL, $app['monolog.handler']); + } + + public function testRedirectLogging() + { + $app = $this->getApplication(); + + $app->get('/foo', function () use ($app) { + return new RedirectResponse('/bar', 302); + }); + + $this->assertFalse($app['monolog.handler']->hasInfoRecords()); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertTrue($app['monolog.handler']->hasDebug('< 302 /bar')); + } + + public function testErrorLoggingGivesWayToSecurityExceptionHandling() + { + $app = $this->getApplication(); + $app['monolog.level'] = Logger::ERROR; + + $app->register(new \Silex\Provider\SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'admin' => array( + 'pattern' => '^/admin', + 'http' => true, + 'users' => array(), + ), + ), + )); + + $app->get('/admin', function () { + return 'SECURE!'; + }); + + $request = Request::create('/admin'); + $app->run($request); + + $this->assertEmpty($app['monolog.handler']->getRecords(), 'Expected no logging to occur'); + } + + public function testStringErrorLevel() + { + $app = $this->getApplication(); + $app['monolog.level'] = 'info'; + + $this->assertSame(Logger::INFO, $app['monolog.handler']->getLevel()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Provided logging level 'foo' does not exist. Must be a valid monolog logging level. + */ + public function testNonExistentStringErrorLevel() + { + $app = $this->getApplication(); + $app['monolog.level'] = 'foo'; + + $app['monolog.handler']->getLevel(); + } + + public function testDisableListener() + { + $app = $this->getApplication(); + unset($app['monolog.listener']); + + $app->handle(Request::create('/404')); + + $this->assertEmpty($app['monolog.handler']->getRecords(), 'Expected no logging to occur'); + } + + public function testExceptionFiltering() + { + $app = new Application(); + $app->get('/foo', function () use ($app) { + throw new NotFoundHttpException(); + }); + + $level = Logger::ERROR; + $app->register(new MonologServiceProvider(), array( + 'monolog.exception.logger_filter' => $app->protect(function () { + return Logger::DEBUG; + }), + 'monolog.handler' => function () use ($app) { + return new TestHandler($app['monolog.level']); + }, + 'monolog.level' => $level, + 'monolog.logfile' => 'php://memory', + )); + + $request = Request::create('/foo'); + $app->handle($request); + + $this->assertCount(0, $app['monolog.handler']->getRecords(), 'Expected no logging to occur'); + } + + protected function assertMatchingRecord($pattern, $level, TestHandler $handler) + { + $found = false; + $records = $handler->getRecords(); + foreach ($records as $record) { + if (preg_match($pattern, $record['message']) && $record['level'] == $level) { + $found = true; + continue; + } + } + $this->assertTrue($found, "Trying to find record matching $pattern with level $level"); + } + + protected function getApplication() + { + $app = new Application(); + + $app->register(new MonologServiceProvider(), array( + 'monolog.handler' => function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + return new TestHandler($level); + }, + 'monolog.logfile' => 'php://memory', + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php new file mode 100644 index 00000000..b027497c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/RememberMeServiceProviderTest.php @@ -0,0 +1,107 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider\RememberMeServiceProvider; +use Silex\Provider\SecurityServiceProvider; +use Silex\Provider\SessionServiceProvider; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\Security\Http\SecurityEvents; + +/** + * SecurityServiceProvider. + * + * @author Fabien Potencier + */ +class RememberMeServiceProviderTest extends WebTestCase +{ + public function testRememberMeAuthentication() + { + $app = $this->createApplication(); + + $interactiveLogin = new InteractiveLoginTriggered(); + $app->on(SecurityEvents::INTERACTIVE_LOGIN, array($interactiveLogin, 'onInteractiveLogin')); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertFalse($interactiveLogin->triggered, 'The interactive login has not been triggered yet'); + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'foo', '_remember_me' => 'true')); + $client->followRedirect(); + $this->assertEquals('AUTHENTICATED_FULLY', $client->getResponse()->getContent()); + $this->assertTrue($interactiveLogin->triggered, 'The interactive login has been triggered'); + + $this->assertNotNull($client->getCookiejar()->get('REMEMBERME'), 'The REMEMBERME cookie is set'); + $event = false; + + $client->getCookiejar()->expire('MOCKSESSID'); + + $client->request('get', '/'); + $this->assertEquals('AUTHENTICATED_REMEMBERED', $client->getResponse()->getContent()); + $this->assertTrue($interactiveLogin->triggered, 'The interactive login has been triggered'); + + $client->request('get', '/logout'); + $client->followRedirect(); + + $this->assertNull($client->getCookiejar()->get('REMEMBERME'), 'The REMEMBERME cookie has been removed'); + } + + public function createApplication($authenticationMethod = 'form') + { + $app = new Application(); + + $app['debug'] = true; + unset($app['exception_handler']); + + $app->register(new SessionServiceProvider(), array( + 'session.test' => true, + )); + $app->register(new SecurityServiceProvider()); + $app->register(new RememberMeServiceProvider()); + + $app['security.firewalls'] = array( + 'http-auth' => array( + 'pattern' => '^.*$', + 'form' => true, + 'remember_me' => array(), + 'logout' => true, + 'users' => array( + 'fabien' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ); + + $app->get('/', function () use ($app) { + if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) { + return 'AUTHENTICATED_FULLY'; + } elseif ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + return 'AUTHENTICATED_REMEMBERED'; + } else { + return 'AUTHENTICATED_ANONYMOUSLY'; + } + }); + + return $app; + } +} + +class InteractiveLoginTriggered +{ + public $triggered = false; + + public function onInteractiveLogin() + { + $this->triggered = true; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php new file mode 100644 index 00000000..da2ca78c --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/RoutingServiceProviderTest.php @@ -0,0 +1,121 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Pimple\Container; +use Silex\Application; +use Silex\Provider\RoutingServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * RoutingProvider test cases. + * + * @author Igor Wiedler + */ +class RoutingServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->get('/hello/{name}', function ($name) {}) + ->bind('hello'); + + $app->get('/', function () {}); + + $request = Request::create('/'); + $app->handle($request); + + $this->assertInstanceOf('Symfony\Component\Routing\Generator\UrlGenerator', $app['url_generator']); + } + + public function testUrlGeneration() + { + $app = new Application(); + + $app->get('/hello/{name}', function ($name) {}) + ->bind('hello'); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('hello', array('name' => 'john')); + }); + + $request = Request::create('/'); + $response = $app->handle($request); + + $this->assertEquals('/hello/john', $response->getContent()); + } + + public function testAbsoluteUrlGeneration() + { + $app = new Application(); + + $app->get('/hello/{name}', function ($name) {}) + ->bind('hello'); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('hello', array('name' => 'john'), UrlGeneratorInterface::ABSOLUTE_URL); + }); + + $request = Request::create('https://localhost:81/'); + $response = $app->handle($request); + + $this->assertEquals('https://localhost:81/hello/john', $response->getContent()); + } + + public function testUrlGenerationWithHttp() + { + $app = new Application(); + + $app->get('/insecure', function () {}) + ->bind('insecure_page') + ->requireHttp(); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('insecure_page'); + }); + + $request = Request::create('https://localhost/'); + $response = $app->handle($request); + + $this->assertEquals('http://localhost/insecure', $response->getContent()); + } + + public function testUrlGenerationWithHttps() + { + $app = new Application(); + + $app->get('/secure', function () {}) + ->bind('secure_page') + ->requireHttps(); + + $app->get('/', function () use ($app) { + return $app['url_generator']->generate('secure_page'); + }); + + $request = Request::create('http://localhost/'); + $response = $app->handle($request); + + $this->assertEquals('https://localhost/secure', $response->getContent()); + } + + public function testControllersFactory() + { + $app = new Container(); + $app->register(new RoutingServiceProvider()); + $coll = $app['controllers_factory']; + $coll->mount('/blog', function ($blog) { + $this->assertInstanceOf('Silex\ControllerCollection', $blog); + }); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php new file mode 100644 index 00000000..3436c53b --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php @@ -0,0 +1,491 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider\SecurityServiceProvider; +use Silex\Provider\SessionServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityServiceProvider. + * + * @author Fabien Potencier + */ +class SecurityServiceProviderTest extends WebTestCase +{ + /** + * @expectedException \LogicException + */ + public function testWrongAuthenticationType() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'wrong' => array( + 'foobar' => true, + 'users' => array(), + ), + ), + )); + $app->get('/', function () {}); + $app->handle(Request::create('/')); + } + + public function testFormAuthentication() + { + $app = $this->createApplication('form'); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals('ANONYMOUS', $client->getResponse()->getContent()); + + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'bar')); + $this->assertContains('Bad credentials', $app['security.last_error']($client->getRequest())); + // hack to re-close the session as the previous assertions re-opens it + $client->getRequest()->getSession()->save(); + + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'foo')); + $this->assertEquals('', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/', $client->getResponse()->getTargetUrl()); + + $client->request('get', '/'); + $this->assertEquals('fabienAUTHENTICATED', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + + $client->request('get', '/logout'); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/', $client->getResponse()->getTargetUrl()); + + $client->request('get', '/'); + $this->assertEquals('ANONYMOUS', $client->getResponse()->getContent()); + + $client->request('get', '/admin'); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/login', $client->getResponse()->getTargetUrl()); + + $client->request('post', '/login_check', array('_username' => 'admin', '_password' => 'foo')); + $this->assertEquals('', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertEquals('http://localhost/admin', $client->getResponse()->getTargetUrl()); + + $client->request('get', '/'); + $this->assertEquals('adminAUTHENTICATEDADMIN', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals('admin', $client->getResponse()->getContent()); + } + + public function testHttpAuthentication() + { + $app = $this->createApplication('http'); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals(401, $client->getResponse()->getStatusCode()); + $this->assertEquals('Basic realm="Secured"', $client->getResponse()->headers->get('www-authenticate')); + + $client->request('get', '/', array(), array(), array('PHP_AUTH_USER' => 'dennis', 'PHP_AUTH_PW' => 'foo')); + $this->assertEquals('dennisAUTHENTICATED', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + + $client->restart(); + + $client->request('get', '/'); + $this->assertEquals(401, $client->getResponse()->getStatusCode()); + $this->assertEquals('Basic realm="Secured"', $client->getResponse()->headers->get('www-authenticate')); + + $client->request('get', '/', array(), array(), array('PHP_AUTH_USER' => 'admin', 'PHP_AUTH_PW' => 'foo')); + $this->assertEquals('adminAUTHENTICATEDADMIN', $client->getResponse()->getContent()); + $client->request('get', '/admin'); + $this->assertEquals('admin', $client->getResponse()->getContent()); + } + + public function testGuardAuthentication() + { + $app = $this->createApplication('guard'); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals(401, $client->getResponse()->getStatusCode(), 'The entry point is configured'); + $this->assertEquals('{"message":"Authentication Required"}', $client->getResponse()->getContent()); + + $client->request('get', '/', array(), array(), array('HTTP_X_AUTH_TOKEN' => 'lili:not the secret')); + $this->assertEquals(403, $client->getResponse()->getStatusCode(), 'User not found'); + $this->assertEquals('{"message":"Username could not be found."}', $client->getResponse()->getContent()); + + $client->request('get', '/', array(), array(), array('HTTP_X_AUTH_TOKEN' => 'victoria:not the secret')); + $this->assertEquals(403, $client->getResponse()->getStatusCode(), 'Invalid credentials'); + $this->assertEquals('{"message":"Invalid credentials."}', $client->getResponse()->getContent()); + + $client->request('get', '/', array(), array(), array('HTTP_X_AUTH_TOKEN' => 'victoria:victoriasecret')); + $this->assertEquals('victoria', $client->getResponse()->getContent()); + } + + public function testUserPasswordValidatorIsRegistered() + { + $app = new Application(); + + $app->register(new ValidatorServiceProvider()); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'admin' => array( + 'pattern' => '^/admin', + 'http' => true, + 'users' => array( + 'admin' => array('ROLE_ADMIN', '513aeb0121909'), + ), + ), + ), + )); + + $app->boot(); + + $this->assertInstanceOf('Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator', $app['security.validator.user_password_validator']); + } + + public function testExposedExceptions() + { + $app = $this->createApplication('form'); + $app['security.hide_user_not_found'] = false; + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals('ANONYMOUS', $client->getResponse()->getContent()); + + $client->request('post', '/login_check', array('_username' => 'fabien', '_password' => 'bar')); + $this->assertEquals('The presented password is invalid.', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + + $client->request('post', '/login_check', array('_username' => 'unknown', '_password' => 'bar')); + $this->assertEquals('Username "unknown" does not exist.', $app['security.last_error']($client->getRequest())); + $client->getRequest()->getSession()->save(); + } + + public function testFakeRoutesAreSerializable() + { + $app = new Application(); + + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'admin' => array( + 'logout' => true, + ), + ), + )); + + $app->boot(); + $app->flush(); + + $this->assertCount(1, unserialize(serialize($app['routes']))); + } + + public function testFirewallWithMethod() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'pattern' => '/', + 'http' => true, + 'methods' => array('POST'), + ), + ), + )); + $app->match('/', function () { return 'foo'; }) + ->method('POST|GET'); + + $request = Request::create('/', 'GET'); + $response = $app->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + + $request = Request::create('/', 'POST'); + $response = $app->handle($request); + $this->assertEquals(401, $response->getStatusCode()); + } + + public function testFirewallWithHost() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'pattern' => '/', + 'http' => true, + 'hosts' => 'localhost2', + ), + ), + )); + $app->get('/', function () { return 'foo'; }) + ->host('localhost2'); + + $app->get('/', function () { return 'foo'; }) + ->host('localhost1'); + + $request = Request::create('http://localhost2/'); + $response = $app->handle($request); + $this->assertEquals(401, $response->getStatusCode()); + + $request = Request::create('http://localhost1/'); + $response = $app->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testUser() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + 'users' => array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + )); + $app->get('/', function () { return 'foo'; }); + + $request = Request::create('/'); + $app->handle($request); + $this->assertNull($app['user']); + + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $app->handle($request); + $this->assertInstanceOf('Symfony\Component\Security\Core\User\UserInterface', $app['user']); + $this->assertEquals('fabien', $app['user']->getUsername()); + } + + public function testUserWithNoToken() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + ), + ), + )); + + $request = Request::create('/'); + + $app->get('/', function () { return 'foo'; }); + $app->handle($request); + $this->assertNull($app['user']); + } + + public function testUserWithInvalidUser() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + ), + ), + )); + + $request = Request::create('/'); + $app->boot(); + $app['security.token_storage']->setToken(new UsernamePasswordToken('foo', 'foo', 'foo')); + + $app->get('/', function () { return 'foo'; }); + $app->handle($request); + $this->assertNull($app['user']); + } + + public function testAccessRulePathArray() + { + $app = new Application(); + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + ), + ), + 'security.access_rules' => array( + array(array( + 'path' => '^/admin', + ), 'ROLE_ADMIN'), + ), + )); + + $request = Request::create('/admin'); + $app->boot(); + $accessMap = $app['security.access_map']; + $this->assertEquals($accessMap->getPatterns($request), array( + array('ROLE_ADMIN'), + '', + )); + } + + public function createApplication($authenticationMethod = 'form') + { + $app = new Application(); + $app->register(new SessionServiceProvider()); + + $app = call_user_func(array($this, 'add'.ucfirst($authenticationMethod).'Authentication'), $app); + + $app['session.test'] = true; + + return $app; + } + + private function addFormAuthentication($app) + { + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'login' => array( + 'pattern' => '^/login$', + ), + 'default' => array( + 'pattern' => '^.*$', + 'anonymous' => true, + 'form' => array( + 'require_previous_session' => false, + ), + 'logout' => true, + 'users' => array( + // password is foo + 'fabien' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + 'admin' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + 'security.access_rules' => array( + array('^/admin', 'ROLE_ADMIN'), + ), + 'security.role_hierarchy' => array( + 'ROLE_ADMIN' => array('ROLE_USER'), + ), + )); + + $app->get('/login', function (Request $request) use ($app) { + $app['session']->start(); + + return $app['security.last_error']($request); + }); + + $app->get('/', function () use ($app) { + $user = $app['security.token_storage']->getToken()->getUser(); + + $content = is_object($user) ? $user->getUsername() : 'ANONYMOUS'; + + if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) { + $content .= 'AUTHENTICATED'; + } + + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + $content .= 'ADMIN'; + } + + return $content; + }); + + $app->get('/admin', function () use ($app) { + return 'admin'; + }); + + return $app; + } + + private function addHttpAuthentication($app) + { + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'http-auth' => array( + 'pattern' => '^.*$', + 'http' => true, + 'users' => array( + // password is foo + 'dennis' => array('ROLE_USER', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + 'admin' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + 'security.access_rules' => array( + array('^/admin', 'ROLE_ADMIN'), + ), + 'security.role_hierarchy' => array( + 'ROLE_ADMIN' => array('ROLE_USER'), + ), + )); + + $app->get('/', function () use ($app) { + $user = $app['security.token_storage']->getToken()->getUser(); + $content = is_object($user) ? $user->getUsername() : 'ANONYMOUS'; + + if ($app['security.authorization_checker']->isGranted('IS_AUTHENTICATED_FULLY')) { + $content .= 'AUTHENTICATED'; + } + + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + $content .= 'ADMIN'; + } + + return $content; + }); + + $app->get('/admin', function () use ($app) { + return 'admin'; + }); + + return $app; + } + + private function addGuardAuthentication($app) + { + $app['app.authenticator.token'] = function ($app) { + return new SecurityServiceProviderTest\TokenAuthenticator($app); + }; + + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'guard' => array( + 'pattern' => '^.*$', + 'form' => true, + 'guard' => array( + 'authenticators' => array( + 'app.authenticator.token', + ), + ), + 'users' => array( + 'victoria' => array('ROLE_USER', 'victoriasecret'), + ), + ), + ), + )); + + $app->get('/', function () use ($app) { + $user = $app['security.token_storage']->getToken()->getUser(); + + $content = is_object($user) ? $user->getUsername() : 'ANONYMOUS'; + + return $content; + })->bind('homepage'); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php new file mode 100644 index 00000000..c569428b --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest/TokenAuthenticator.php @@ -0,0 +1,79 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider\SecurityServiceProviderTest; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * This class is used to test "guard" authentication with the SecurityServiceProvider. + */ +class TokenAuthenticator extends AbstractGuardAuthenticator +{ + public function getCredentials(Request $request) + { + if (!$token = $request->headers->get('X-AUTH-TOKEN')) { + return; + } + + list($username, $secret) = explode(':', $token); + + return array( + 'username' => $username, + 'secret' => $secret, + ); + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + return $userProvider->loadUserByUsername($credentials['username']); + } + + public function checkCredentials($credentials, UserInterface $user) + { + // This is not a safe way of validating a password. + return $user->getPassword() === $credentials['secret']; + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + return; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + { + $data = array( + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()), + ); + + return new JsonResponse($data, 403); + } + + public function start(Request $request, AuthenticationException $authException = null) + { + $data = array( + 'message' => 'Authentication Required', + ); + + return new JsonResponse($data, 401); + } + + public function supportsRememberMe() + { + return false; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php new file mode 100644 index 00000000..0b143f5f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SerializerServiceProviderTest.php @@ -0,0 +1,36 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\SerializerServiceProvider; + +/** + * SerializerServiceProvider test cases. + * + * @author Fabien Potencier + */ +class SerializerServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->register(new SerializerServiceProvider()); + + $this->assertInstanceOf("Symfony\Component\Serializer\Serializer", $app['serializer']); + $this->assertTrue($app['serializer']->supportsEncoding('xml')); + $this->assertTrue($app['serializer']->supportsEncoding('json')); + $this->assertTrue($app['serializer']->supportsDecoding('xml')); + $this->assertTrue($app['serializer']->supportsDecoding('json')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php new file mode 100644 index 00000000..fb7ae0cf --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.php @@ -0,0 +1,126 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\WebTestCase; +use Silex\Provider\SessionServiceProvider; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpFoundation\Session; + +/** + * SessionProvider test cases. + * + * @author Igor Wiedler + * @author Fabien Potencier + */ +class SessionServiceProviderTest extends WebTestCase +{ + public function testRegister() + { + /* + * Smoke test + */ + $defaultStorage = $this->app['session.storage.native']; + + $client = $this->createClient(); + + $client->request('get', '/login'); + $this->assertEquals('Logged in successfully.', $client->getResponse()->getContent()); + + $client->request('get', '/account'); + $this->assertEquals('This is your account.', $client->getResponse()->getContent()); + + $client->request('get', '/logout'); + $this->assertEquals('Logged out successfully.', $client->getResponse()->getContent()); + + $client->request('get', '/account'); + $this->assertEquals('You are not logged in.', $client->getResponse()->getContent()); + } + + public function createApplication() + { + $app = new Application(); + + $app->register(new SessionServiceProvider(), array( + 'session.test' => true, + )); + + $app->get('/login', function () use ($app) { + $app['session']->set('logged_in', true); + + return 'Logged in successfully.'; + }); + + $app->get('/account', function () use ($app) { + if (!$app['session']->get('logged_in')) { + return 'You are not logged in.'; + } + + return 'This is your account.'; + }); + + $app->get('/logout', function () use ($app) { + $app['session']->invalidate(); + + return 'Logged out successfully.'; + }); + + return $app; + } + + public function testWithRoutesThatDoesNotUseSession() + { + $app = new Application(); + + $app->register(new SessionServiceProvider(), array( + 'session.test' => true, + )); + + $app->get('/', function () { + return 'A welcome page.'; + }); + + $app->get('/robots.txt', function () { + return 'Informations for robots.'; + }); + + $app['debug'] = true; + unset($app['exception_handler']); + + $client = new Client($app); + + $client->request('get', '/'); + $this->assertEquals('A welcome page.', $client->getResponse()->getContent()); + + $client->request('get', '/robots.txt'); + $this->assertEquals('Informations for robots.', $client->getResponse()->getContent()); + } + + public function testSessionRegister() + { + $app = new Application(); + + $attrs = new Session\Attribute\AttributeBag(); + $flash = new Session\Flash\FlashBag(); + $app->register(new SessionServiceProvider(), array( + 'session.attribute_bag' => $attrs, + 'session.flash_bag' => $flash, + 'session.test' => true, + )); + + $session = $app['session']; + + $this->assertSame($flash, $session->getBag('flashes')); + $this->assertSame($attrs, $session->getBag('attributes')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php new file mode 100644 index 00000000..c1eb2b0f --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SpoolStub.php @@ -0,0 +1,47 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +class SpoolStub implements \Swift_Spool +{ + private $messages = array(); + public $hasFlushed = false; + + public function getMessages() + { + return $this->messages; + } + + public function start() + { + } + + public function stop() + { + } + + public function isStarted() + { + return count($this->messages) > 0; + } + + public function queueMessage(\Swift_Mime_Message $message) + { + $this->messages[] = clone $message; + } + + public function flushQueue(\Swift_Transport $transport, &$failedRecipients = null) + { + $this->hasFlushed = true; + $this->messages = array(); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php new file mode 100644 index 00000000..f9ddc4aa --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php @@ -0,0 +1,133 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\SwiftmailerServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +class SwiftmailerServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testSwiftMailerServiceIsSwiftMailer() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $this->assertInstanceOf('Swift_Mailer', $app['mailer']); + } + + public function testSwiftMailerIgnoresSpoolIfDisabled() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.use_spool'] = false; + + $app['swiftmailer.spooltransport'] = function () { + throw new \Exception('Should not be instantiated'); + }; + + $this->assertInstanceOf('Swift_Mailer', $app['mailer']); + } + + public function testSwiftMailerSendsMailsOnFinish() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.spool'] = function () { + return new SpoolStub(); + }; + + $app->get('/', function () use ($app) { + $app['mailer']->send(\Swift_Message::newInstance()); + }); + + $this->assertCount(0, $app['swiftmailer.spool']->getMessages()); + + $request = Request::create('/'); + $response = $app->handle($request); + $this->assertCount(1, $app['swiftmailer.spool']->getMessages()); + + $app->terminate($request, $response); + $this->assertTrue($app['swiftmailer.spool']->hasFlushed); + $this->assertCount(0, $app['swiftmailer.spool']->getMessages()); + } + + public function testSwiftMailerAvoidsFlushesIfMailerIsUnused() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.spool'] = function () { + return new SpoolStub(); + }; + + $app->get('/', function () use ($app) { }); + + $request = Request::create('/'); + $response = $app->handle($request); + $this->assertCount(0, $app['swiftmailer.spool']->getMessages()); + + $app->terminate($request, $response); + $this->assertFalse($app['swiftmailer.spool']->hasFlushed); + } + + public function testSwiftMailerSenderAddress() + { + $app = new Application(); + + $app->register(new SwiftmailerServiceProvider()); + $app->boot(); + + $app['swiftmailer.spool'] = function () { + return new SpoolStub(); + }; + + $app['swiftmailer.sender_address'] = 'foo@example.com'; + + $app['mailer']->send(\Swift_Message::newInstance()); + + $messages = $app['swiftmailer.spool']->getMessages(); + $this->assertCount(1, $messages); + + list($message) = $messages; + $this->assertEquals('foo@example.com', $message->getReturnPath()); + } + + public function testSwiftMailerPlugins() + { + $plugin = $this->getMockBuilder('Swift_Events_TransportChangeListener')->getMock(); + $plugin->expects($this->once())->method('beforeTransportStarted'); + + $app = new Application(); + $app->boot(); + + $app->register(new SwiftmailerServiceProvider()); + + $app['swiftmailer.plugins'] = function ($app) use ($plugin) { + return array($plugin); + }; + + $dispatcher = $app['swiftmailer.transport.eventdispatcher']; + $event = $dispatcher->createTransportChangeEvent(new \Swift_Transport_NullTransport($dispatcher)); + $dispatcher->dispatchEvent($event, 'beforeTransportStarted'); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php new file mode 100644 index 00000000..6d893f98 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php @@ -0,0 +1,181 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\TranslationServiceProvider; +use Silex\Provider\LocaleServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * TranslationProvider test cases. + * + * @author Daniel Tschinder + */ +class TranslationServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @return Application + */ + protected function getPreparedApp() + { + $app = new Application(); + + $app->register(new LocaleServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app['translator.domains'] = array( + 'messages' => array( + 'en' => array( + 'key1' => 'The translation', + 'key_only_english' => 'Foo', + 'key2' => 'One apple|%count% apples', + 'test' => array( + 'key' => 'It works', + ), + ), + 'de' => array( + 'key1' => 'The german translation', + 'key2' => 'One german apple|%count% german apples', + 'test' => array( + 'key' => 'It works in german', + ), + ), + ), + ); + + return $app; + } + + public function transChoiceProvider() + { + return array( + array('key2', 0, null, '0 apples'), + array('key2', 1, null, 'One apple'), + array('key2', 2, null, '2 apples'), + array('key2', 0, 'de', '0 german apples'), + array('key2', 1, 'de', 'One german apple'), + array('key2', 2, 'de', '2 german apples'), + array('key2', 0, 'ru', '0 apples'), // fallback + array('key2', 1, 'ru', 'One apple'), // fallback + array('key2', 2, 'ru', '2 apples'), // fallback + ); + } + + public function transProvider() + { + return array( + array('key1', null, 'The translation'), + array('key1', 'de', 'The german translation'), + array('key1', 'ru', 'The translation'), // fallback + array('test.key', null, 'It works'), + array('test.key', 'de', 'It works in german'), + array('test.key', 'ru', 'It works'), // fallback + ); + } + + /** + * @dataProvider transProvider + */ + public function testTransForDefaultLanguage($key, $locale, $expected) + { + $app = $this->getPreparedApp(); + + $result = $app['translator']->trans($key, array(), null, $locale); + + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider transChoiceProvider + */ + public function testTransChoiceForDefaultLanguage($key, $number, $locale, $expected) + { + $app = $this->getPreparedApp(); + + $result = $app['translator']->transChoice($key, $number, array('%count%' => $number), null, $locale); + $this->assertEquals($expected, $result); + } + + public function testFallbacks() + { + $app = $this->getPreparedApp(); + $app['locale_fallbacks'] = array('de', 'en'); + + // fallback to english + $result = $app['translator']->trans('key_only_english', array(), null, 'ru'); + $this->assertEquals('Foo', $result); + + // fallback to german + $result = $app['translator']->trans('key1', array(), null, 'ru'); + $this->assertEquals('The german translation', $result); + } + + public function testLocale() + { + $app = $this->getPreparedApp(); + $app->get('/', function () use ($app) { return $app['translator']->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('en', $response->getContent()); + + $app = $this->getPreparedApp(); + $app->get('/', function () use ($app) { return $app['translator']->getLocale(); }); + $request = Request::create('/'); + $request->setLocale('fr'); + $response = $app->handle($request); + $this->assertEquals('fr', $response->getContent()); + + $app = $this->getPreparedApp(); + $app->get('/{_locale}', function () use ($app) { return $app['translator']->getLocale(); }); + $response = $app->handle(Request::create('/es')); + $this->assertEquals('es', $response->getContent()); + } + + public function testLocaleInSubRequests() + { + $app = $this->getPreparedApp(); + $app->get('/embed/{_locale}', function () use ($app) { return $app['translator']->getLocale(); }); + $app->get('/{_locale}', function () use ($app) { + return $app['translator']->getLocale(). + $app->handle(Request::create('/embed/es'), HttpKernelInterface::SUB_REQUEST)->getContent(). + $app['translator']->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + $this->assertEquals('fresfr', $response->getContent()); + + $app = $this->getPreparedApp(); + $app->get('/embed', function () use ($app) { return $app['translator']->getLocale(); }); + $app->get('/{_locale}', function () use ($app) { + return $app['translator']->getLocale(). + $app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent(). + $app['translator']->getLocale(); + }); + $response = $app->handle(Request::create('/fr')); + // locale in sub-request must be "en" as this is the value if the sub-request is converted to an ESI + $this->assertEquals('frenfr', $response->getContent()); + } + + public function testLocaleWithBefore() + { + $app = $this->getPreparedApp(); + $app->before(function (Request $request) { $request->setLocale('fr'); }, Application::EARLY_EVENT); + $app->get('/embed', function () use ($app) { return $app['translator']->getLocale(); }); + $app->get('/', function () use ($app) { + return $app['translator']->getLocale(). + $app->handle(Request::create('/embed'), HttpKernelInterface::SUB_REQUEST)->getContent(). + $app['translator']->getLocale(); + }); + $response = $app->handle(Request::create('/')); + // locale in sub-request is "en" as the before filter is only executed for the main request + $this->assertEquals('frenfr', $response->getContent()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php new file mode 100644 index 00000000..501036c5 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.php @@ -0,0 +1,163 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Fig\Link\Link; +use Silex\Application; +use Silex\Provider\CsrfServiceProvider; +use Silex\Provider\FormServiceProvider; +use Silex\Provider\TwigServiceProvider; +use Silex\Provider\AssetServiceProvider; +use Symfony\Bridge\Twig\Extension\WebLinkExtension; +use Symfony\Component\HttpFoundation\Request; + +/** + * TwigProvider test cases. + * + * @author Igor Wiedler + */ +class TwigServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegisterAndRender() + { + $app = new Application(); + + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('hello' => 'Hello {{ name }}!'), + )); + + $app->get('/hello/{name}', function ($name) use ($app) { + return $app['twig']->render('hello', array('name' => $name)); + }); + + $request = Request::create('/hello/john'); + $response = $app->handle($request); + $this->assertEquals('Hello john!', $response->getContent()); + } + + public function testLoaderPriority() + { + $app = new Application(); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('foo' => 'foo'), + )); + $loader = $this->getMockBuilder('\Twig_LoaderInterface')->getMock(); + $loader->expects($this->never())->method('getSourceContext'); + $app['twig.loader.filesystem'] = function ($app) use ($loader) { + return $loader; + }; + $this->assertEquals('foo', $app['twig.loader']->getSourceContext('foo')->getCode()); + } + + public function testHttpFoundationIntegration() + { + $app = new Application(); + $app['request_stack']->push(Request::create('/dir1/dir2/file')); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'absolute' => '{{ absolute_url("foo.css") }}', + 'relative' => '{{ relative_path("/dir1/foo.css") }}', + ), + )); + + $this->assertEquals('http://localhost/dir1/dir2/foo.css', $app['twig']->render('absolute')); + $this->assertEquals('../foo.css', $app['twig']->render('relative')); + } + + public function testAssetIntegration() + { + $app = new Application(); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('hello' => '{{ asset("/foo.css") }}'), + )); + $app->register(new AssetServiceProvider(), array( + 'assets.version' => 1, + )); + + $this->assertEquals('/foo.css?1', $app['twig']->render('hello')); + } + + public function testGlobalVariable() + { + $app = new Application(); + $app['request_stack']->push(Request::create('/?name=Fabien')); + + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array('hello' => '{{ global.request.get("name") }}'), + )); + + $this->assertEquals('Fabien', $app['twig']->render('hello')); + } + + public function testFormFactory() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new CsrfServiceProvider()); + $app->register(new TwigServiceProvider()); + + $this->assertInstanceOf('Twig_Environment', $app['twig'], 'Service twig is created successful.'); + $this->assertInstanceOf('Symfony\Bridge\Twig\Form\TwigRendererEngine', $app['twig.form.engine'], 'Service twig.form.engine is created successful.'); + $this->assertInstanceOf('Symfony\Bridge\Twig\Form\TwigRenderer', $app['twig.form.renderer'], 'Service twig.form.renderer is created successful.'); + } + + public function testFormWithoutCsrf() + { + $app = new Application(); + $app->register(new FormServiceProvider()); + $app->register(new TwigServiceProvider()); + + $this->assertInstanceOf('Twig_Environment', $app['twig']); + } + + public function testFormatParameters() + { + $app = new Application(); + + $timezone = new \DateTimeZone('Europe/Paris'); + + $app->register(new TwigServiceProvider(), array( + 'twig.date.format' => 'Y-m-d', + 'twig.date.interval_format' => '%h hours', + 'twig.date.timezone' => $timezone, + 'twig.number_format.decimals' => 2, + 'twig.number_format.decimal_point' => ',', + 'twig.number_format.thousands_separator' => ' ', + )); + + $twig = $app['twig']; + + $this->assertSame(array('Y-m-d', '%h hours'), $twig->getExtension('Twig_Extension_Core')->getDateFormat()); + $this->assertSame($timezone, $twig->getExtension('Twig_Extension_Core')->getTimezone()); + $this->assertSame(array(2, ',', ' '), $twig->getExtension('Twig_Extension_Core')->getNumberFormat()); + } + + public function testWebLinkIntegration() + { + if (!class_exists(WebLinkExtension::class)) { + $this->markTestSkipped('Twig WebLink extension not available.'); + } + + $app = new Application(); + $app['request_stack']->push($request = Request::create('/')); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'preload' => '{{ preload("/foo.css") }}', + ), + )); + + $this->assertEquals('/foo.css', $app['twig']->render('preload')); + + $link = new Link('preload', '/foo.css'); + $this->assertEquals(array($link), array_values($request->attributes->get('_links')->getLinks())); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php new file mode 100644 index 00000000..a28ccf78 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php @@ -0,0 +1,194 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\TranslationServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Silex\Provider\FormServiceProvider; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Validator\Constraints as Assert; +use Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint\Custom; +use Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint\CustomValidator; +use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * ValidatorServiceProvider. + * + * Javier Lopez + */ +class ValidatorServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegister() + { + $app = new Application(); + $app->register(new ValidatorServiceProvider()); + $app->register(new FormServiceProvider()); + + return $app; + } + + public function testRegisterWithCustomValidators() + { + $app = new Application(); + + $app['custom.validator'] = function () { + return new CustomValidator(); + }; + + $app->register(new ValidatorServiceProvider(), array( + 'validator.validator_service_ids' => array( + 'test.custom.validator' => 'custom.validator', + ), + )); + + return $app; + } + + /** + * @depends testRegisterWithCustomValidators + */ + public function testConstraintValidatorFactory($app) + { + $this->assertInstanceOf('Silex\Provider\Validator\ConstraintValidatorFactory', $app['validator.validator_factory']); + + $validator = $app['validator.validator_factory']->getInstance(new Custom()); + $this->assertInstanceOf('Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint\CustomValidator', $validator); + } + + /** + * @depends testRegister + */ + public function testConstraintValidatorFactoryWithExpression($app) + { + $constraint = new Assert\Expression('true'); + $validator = $app['validator.validator_factory']->getInstance($constraint); + $this->assertInstanceOf('Symfony\Component\Validator\Constraints\ExpressionValidator', $validator); + } + + /** + * @depends testRegister + */ + public function testValidatorServiceIsAValidator($app) + { + $this->assertTrue($app['validator'] instanceof ValidatorInterface || $app['validator'] instanceof LegacyValidatorInterface ); + } + + /** + * @depends testRegister + * @dataProvider getTestValidatorConstraintProvider + */ + public function testValidatorConstraint($email, $isValid, $nbGlobalError, $nbEmailError, $app) + { + $constraints = new Assert\Collection(array( + 'email' => array( + new Assert\NotBlank(), + new Assert\Email(), + ), + )); + + $builder = $app['form.factory']->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', array(), array( + 'constraints' => $constraints, + )); + + $form = $builder + ->add('email', 'Symfony\Component\Form\Extension\Core\Type\EmailType', array('label' => 'Email')) + ->getForm() + ; + + $form->submit(array('email' => $email)); + + $this->assertEquals($isValid, $form->isValid()); + $this->assertEquals($nbGlobalError, count($form->getErrors())); + $this->assertEquals($nbEmailError, count($form->offsetGet('email')->getErrors())); + } + + public function testValidatorWillNotAddNonexistentTranslationFiles() + { + $app = new Application(array( + 'locale' => 'nonexistent', + )); + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider(), array( + 'locale_fallbacks' => array(), + )); + + $app['validator']; + $translator = $app['translator']; + + try { + $translator->trans('test'); + } catch (NotFoundResourceException $e) { + $this->fail('Validator should not add a translation resource that does not exist'); + } + } + + public function getTestValidatorConstraintProvider() + { + // Email, form is valid, nb global error, nb email error + return array( + array('', false, 0, 1), + array('not an email', false, 0, 1), + array('email@sample.com', true, 0, 0), + ); + } + + /** + * @dataProvider getAddResourceData + */ + public function testAddResource($registerValidatorFirst) + { + $app = new Application(); + $app['locale'] = 'fr'; + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app['translator'] = $app->extend('translator', function ($translator, $app) { + $translator->addResource('array', array('This value should not be blank.' => 'Pas vide'), 'fr', 'validators'); + + return $translator; + }); + + if ($registerValidatorFirst) { + $app['validator']; + } + + $this->assertEquals('Pas vide', $app['translator']->trans('This value should not be blank.', array(), 'validators', 'fr')); + } + + public function getAddResourceData() + { + return array(array(false), array(true)); + } + + public function testAddResourceAlternate() + { + $app = new Application(); + $app['locale'] = 'fr'; + + $app->register(new ValidatorServiceProvider()); + $app->register(new TranslationServiceProvider()); + $app->factory($app->extend('translator.resources', function ($resources, $app) { + $resources = array_merge($resources, array( + array('array', array('This value should not be blank.' => 'Pas vide'), 'fr', 'validators'), + )); + + return $resources; + })); + + $app['validator']; + + $this->assertEquals('Pas vide', $app['translator']->trans('This value should not be blank.', array(), 'validators', 'fr')); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php new file mode 100644 index 00000000..bef911aa --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/Custom.php @@ -0,0 +1,29 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint; + +use Symfony\Component\Validator\Constraint; + +/** + * @author Alex Kalyvitis + */ +class Custom extends Constraint +{ + public $message = 'This field must be ...'; + public $table; + public $field; + + public function validatedBy() + { + return 'test.custom.validator'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php new file mode 100644 index 00000000..856927db --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest/Constraint/CustomValidator.php @@ -0,0 +1,32 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider\ValidatorServiceProviderTest\Constraint; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + +/** + * @author Alex Kalyvitis + */ +class CustomValidator extends ConstraintValidator +{ + public function isValid($value, Constraint $constraint) + { + // Validate... + return true; + } + + public function validate($value, Constraint $constraint) + { + return $this->isValid($value, $constraint); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php new file mode 100644 index 00000000..48237194 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityRoute.php @@ -0,0 +1,19 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Route; + +use Silex\Route; + +class SecurityRoute extends Route +{ + use Route\SecurityTrait; +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php new file mode 100644 index 00000000..352a77bd --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php @@ -0,0 +1,85 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Route; + +use Silex\Application; +use Silex\Provider\SecurityServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityTrait test cases. + * + * @author Fabien Potencier + */ +class SecurityTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testSecureWithNoAuthenticatedUser() + { + $app = $this->createApplication(); + + $app->get('/', function () { return 'foo'; }) + ->secure('ROLE_ADMIN') + ; + + $request = Request::create('/'); + $response = $app->handle($request); + $this->assertEquals(401, $response->getStatusCode()); + } + + public function testSecureWithAuthorizedRoles() + { + $app = $this->createApplication(); + + $app->get('/', function () { return 'foo'; }) + ->secure('ROLE_ADMIN') + ; + + $request = Request::create('/'); + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $response = $app->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + } + + public function testSecureWithUnauthorizedRoles() + { + $app = $this->createApplication(); + + $app->get('/', function () { return 'foo'; }) + ->secure('ROLE_SUPER_ADMIN') + ; + + $request = Request::create('/'); + $request->headers->set('PHP_AUTH_USER', 'fabien'); + $request->headers->set('PHP_AUTH_PW', 'foo'); + $response = $app->handle($request); + $this->assertEquals(403, $response->getStatusCode()); + } + + private function createApplication() + { + $app = new Application(); + $app['route_class'] = 'Silex\Tests\Route\SecurityRoute'; + $app->register(new SecurityServiceProvider(), array( + 'security.firewalls' => array( + 'default' => array( + 'http' => true, + 'users' => array( + 'fabien' => array('ROLE_ADMIN', '$2y$15$lzUNsTegNXvZW3qtfucV0erYBcEqWVeyOmjolB7R1uodsAVJ95vvu'), + ), + ), + ), + )); + + return $app; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/RouterTest.php b/vendor/silex/silex/tests/Silex/Tests/RouterTest.php new file mode 100644 index 00000000..665891ea --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/RouterTest.php @@ -0,0 +1,285 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; + +/** + * Router test cases. + * + * @author Igor Wiedler + */ +class RouterTest extends \PHPUnit_Framework_TestCase +{ + public function testMapRouting() + { + $app = new Application(); + + $app->match('/foo', function () { + return 'foo'; + }); + + $app->match('/bar', function () { + return 'bar'; + }); + + $app->match('/', function () { + return 'root'; + }); + + $this->checkRouteResponse($app, '/foo', 'foo'); + $this->checkRouteResponse($app, '/bar', 'bar'); + $this->checkRouteResponse($app, '/', 'root'); + } + + public function testStatusCode() + { + $app = new Application(); + + $app->put('/created', function () { + return new Response('', 201); + }); + + $app->match('/forbidden', function () { + return new Response('', 403); + }); + + $app->match('/not_found', function () { + return new Response('', 404); + }); + + $request = Request::create('/created', 'put'); + $response = $app->handle($request); + $this->assertEquals(201, $response->getStatusCode()); + + $request = Request::create('/forbidden'); + $response = $app->handle($request); + $this->assertEquals(403, $response->getStatusCode()); + + $request = Request::create('/not_found'); + $response = $app->handle($request); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testRedirect() + { + $app = new Application(); + + $app->match('/redirect', function () { + return new RedirectResponse('/target'); + }); + + $app->match('/redirect2', function () use ($app) { + return $app->redirect('/target2'); + }); + + $request = Request::create('/redirect'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('/target')); + + $request = Request::create('/redirect2'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('/target2')); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testMissingRoute() + { + $app = new Application(); + unset($app['exception_handler']); + + $request = Request::create('/baz'); + $app->handle($request); + } + + public function testMethodRouting() + { + $app = new Application(); + + $app->match('/foo', function () { + return 'foo'; + }); + + $app->match('/bar', function () { + return 'bar'; + })->method('GET|POST'); + + $app->get('/resource', function () { + return 'get resource'; + }); + + $app->post('/resource', function () { + return 'post resource'; + }); + + $app->put('/resource', function () { + return 'put resource'; + }); + + $app->patch('/resource', function () { + return 'patch resource'; + }); + + $app->delete('/resource', function () { + return 'delete resource'; + }); + + $this->checkRouteResponse($app, '/foo', 'foo'); + $this->checkRouteResponse($app, '/bar', 'bar'); + $this->checkRouteResponse($app, '/bar', 'bar', 'post'); + $this->checkRouteResponse($app, '/resource', 'get resource'); + $this->checkRouteResponse($app, '/resource', 'post resource', 'post'); + $this->checkRouteResponse($app, '/resource', 'put resource', 'put'); + $this->checkRouteResponse($app, '/resource', 'patch resource', 'patch'); + $this->checkRouteResponse($app, '/resource', 'delete resource', 'delete'); + } + + public function testRequestShouldBeStoredRegardlessOfRouting() + { + $app = new Application(); + + $app->get('/foo', function (Request $request) use ($app) { + return new Response($request->getRequestUri()); + }); + + $app->error(function ($e, Request $request, $code) use ($app) { + return new Response($request->getRequestUri()); + }); + + foreach (array('/foo', '/bar') as $path) { + $request = Request::create($path); + $response = $app->handle($request); + $this->assertContains($path, $response->getContent()); + } + } + + public function testTrailingSlashBehavior() + { + $app = new Application(); + + $app->get('/foo/', function () use ($app) { + return new Response('ok'); + }); + + $request = Request::create('/foo'); + $response = $app->handle($request); + + $this->assertEquals(301, $response->getStatusCode()); + $this->assertEquals('/foo/', $response->getTargetUrl()); + } + + public function testHostSpecification() + { + $route = new \Silex\Route(); + + $this->assertSame($route, $route->host('{locale}.example.com')); + $this->assertEquals('{locale}.example.com', $route->getHost()); + } + + public function testRequireHttpRedirect() + { + $app = new Application(); + + $app->match('/secured', function () { + return 'secured content'; + }) + ->requireHttp(); + + $request = Request::create('https://example.com/secured'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('http://example.com/secured')); + } + + public function testRequireHttpsRedirect() + { + $app = new Application(); + + $app->match('/secured', function () { + return 'secured content'; + }) + ->requireHttps(); + + $request = Request::create('http://example.com/secured'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('https://example.com/secured')); + } + + public function testRequireHttpsRedirectIncludesQueryString() + { + $app = new Application(); + + $app->match('/secured', function () { + return 'secured content'; + }) + ->requireHttps(); + + $request = Request::create('http://example.com/secured?query=string'); + $response = $app->handle($request); + $this->assertTrue($response->isRedirect('https://example.com/secured?query=string')); + } + + public function testConditionOnRoute() + { + $app = new Application(); + $app->match('/secured', function () { + return 'secured content'; + }) + ->when('request.isSecure() == true'); + + $request = Request::create('http://example.com/secured'); + $response = $app->handle($request); + $this->assertEquals(404, $response->getStatusCode()); + } + + public function testClassNameControllerSyntax() + { + $app = new Application(); + + $app->get('/foo', 'Silex\Tests\MyController::getFoo'); + + $this->checkRouteResponse($app, '/foo', 'foo'); + } + + public function testClassNameControllerSyntaxWithStaticMethod() + { + $app = new Application(); + + $app->get('/bar', 'Silex\Tests\MyController::getBar'); + + $this->checkRouteResponse($app, '/bar', 'bar'); + } + + protected function checkRouteResponse(Application $app, $path, $expectedContent, $method = 'get', $message = null) + { + $request = Request::create($path, $method); + $response = $app->handle($request); + $this->assertEquals($expectedContent, $response->getContent(), $message); + } +} + +class MyController +{ + public function getFoo() + { + return 'foo'; + } + + public static function getBar() + { + return 'bar'; + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php new file mode 100644 index 00000000..4bc88a45 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.php @@ -0,0 +1,43 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\Provider\ServiceControllerServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +/** + * Router test cases, using the ServiceControllerResolver. + */ +class ServiceControllerResolverRouterTest extends RouterTest +{ + public function testServiceNameControllerSyntax() + { + $app = new Application(); + $app->register(new ServiceControllerServiceProvider()); + + $app['service_name'] = function () { + return new MyController(); + }; + + $app->get('/bar', 'service_name:getBar'); + + $this->checkRouteResponse($app, '/bar', 'bar'); + } + + protected function checkRouteResponse(Application $app, $path, $expectedContent, $method = 'get', $message = null) + { + $request = Request::create($path, $method); + $response = $app->handle($request); + $this->assertEquals($expectedContent, $response->getContent(), $message); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php new file mode 100644 index 00000000..f732c7de --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Tests; + +use Silex\ServiceControllerResolver; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * Unit tests for ServiceControllerResolver, see ServiceControllerResolverRouterTest for some + * integration tests. + */ +class ServiceControllerResolverTest extends \PHPUnit_Framework_Testcase +{ + private $app; + private $mockCallbackResolver; + private $mockResolver; + private $resolver; + + public function setup() + { + $this->mockResolver = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->mockCallbackResolver = $this->getMockBuilder('Silex\CallbackResolver') + ->disableOriginalConstructor() + ->getMock(); + + $this->app = new Application(); + $this->resolver = new ServiceControllerResolver($this->mockResolver, $this->mockCallbackResolver); + } + + public function testShouldResolveServiceController() + { + $this->mockCallbackResolver->expects($this->once()) + ->method('isValid') + ->will($this->returnValue(true)); + + $this->mockCallbackResolver->expects($this->once()) + ->method('convertCallback') + ->with('some_service:methodName') + ->will($this->returnValue(array('callback'))); + + $this->app['some_service'] = function () { return new \stdClass(); }; + + $req = Request::create('/'); + $req->attributes->set('_controller', 'some_service:methodName'); + + $this->assertEquals(array('callback'), $this->resolver->getController($req)); + } + + public function testShouldUnresolvedControllerNames() + { + $req = Request::create('/'); + $req->attributes->set('_controller', 'some_class::methodName'); + + $this->mockCallbackResolver->expects($this->once()) + ->method('isValid') + ->with('some_class::methodName') + ->will($this->returnValue(false)); + + $this->mockResolver->expects($this->once()) + ->method('getController') + ->with($req) + ->will($this->returnValue(123)); + + $this->assertEquals(123, $this->resolver->getController($req)); + } + + public function testShouldDelegateGetArguments() + { + $req = Request::create('/'); + $this->mockResolver->expects($this->once()) + ->method('getArguments') + ->with($req) + ->will($this->returnValue(123)); + + $this->assertEquals(123, $this->resolver->getArguments($req, function () {})); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/StreamTest.php b/vendor/silex/silex/tests/Silex/Tests/StreamTest.php new file mode 100644 index 00000000..601f0e60 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/StreamTest.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * Stream test cases. + * + * @author Igor Wiedler + */ +class StreamTest extends \PHPUnit_Framework_TestCase +{ + public function testStreamReturnsStreamingResponse() + { + $app = new Application(); + + $response = $app->stream(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response); + $this->assertSame(false, $response->getContent()); + } + + public function testStreamActuallyStreams() + { + $i = 0; + + $stream = function () use (&$i) { + ++$i; + }; + + $app = new Application(); + $response = $app->stream($stream); + + $this->assertEquals(0, $i); + + $request = Request::create('/stream'); + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(1, $i); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php b/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php new file mode 100644 index 00000000..474ffc38 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.php @@ -0,0 +1,78 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +use Silex\WebTestCase; +use Symfony\Component\HttpFoundation\Request; + +/** + * Functional test cases. + * + * @author Igor Wiedler + */ +class WebTestCaseTest extends WebTestCase +{ + public function createApplication() + { + $app = new Application(); + + $app->match('/hello', function () { + return 'world'; + }); + + $app->match('/html', function () { + return '

title

'; + }); + + $app->match('/server', function (Request $request) use ($app) { + $user = $request->server->get('PHP_AUTH_USER'); + $pass = $request->server->get('PHP_AUTH_PW'); + + return "

$user:$pass

"; + }); + + return $app; + } + + public function testGetHello() + { + $client = $this->createClient(); + + $client->request('GET', '/hello'); + $response = $client->getResponse(); + $this->assertTrue($response->isSuccessful()); + $this->assertEquals('world', $response->getContent()); + } + + public function testCrawlerFilter() + { + $client = $this->createClient(); + + $crawler = $client->request('GET', '/html'); + $this->assertEquals('title', $crawler->filter('h1')->text()); + } + + public function testServerVariables() + { + $user = 'klaus'; + $pass = '123456'; + + $client = $this->createClient(array( + 'PHP_AUTH_USER' => $user, + 'PHP_AUTH_PW' => $pass, + )); + + $crawler = $client->request('GET', '/server'); + $this->assertEquals("$user:$pass", $crawler->filter('h1')->text()); + } +} diff --git a/vendor/symfony/debug/.gitignore b/vendor/symfony/debug/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/debug/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/debug/BufferingLogger.php b/vendor/symfony/debug/BufferingLogger.php new file mode 100644 index 00000000..a2ed75b9 --- /dev/null +++ b/vendor/symfony/debug/BufferingLogger.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\Debug; + +use Psr\Log\AbstractLogger; + +/** + * A buffering logger that stacks logs for later. + * + * @author Nicolas Grekas + */ +class BufferingLogger extends AbstractLogger +{ + private $logs = array(); + + public function log($level, $message, array $context = array()) + { + $this->logs[] = array($level, $message, $context); + } + + public function cleanLogs() + { + $logs = $this->logs; + $this->logs = array(); + + return $logs; + } +} diff --git a/vendor/symfony/debug/CHANGELOG.md b/vendor/symfony/debug/CHANGELOG.md new file mode 100644 index 00000000..a853b7a0 --- /dev/null +++ b/vendor/symfony/debug/CHANGELOG.md @@ -0,0 +1,59 @@ +CHANGELOG +========= + +3.3.0 +----- + +* deprecated the `ContextErrorException` class: use \ErrorException directly now + +3.2.0 +----- + +* `FlattenException::getTrace()` now returns additional type descriptions + `integer` and `float`. + + +3.0.0 +----- + +* removed classes, methods and interfaces deprecated in 2.x + +2.8.0 +----- + +* added BufferingLogger for errors that happen before a proper logger is configured +* allow throwing from `__toString()` with `return trigger_error($e, E_USER_ERROR);` +* deprecate ExceptionHandler::createResponse + +2.7.0 +----- + +* added deprecations checking for parent interfaces/classes to DebugClassLoader +* added ZTS support to symfony_debug extension +* added symfony_debug_backtrace() to symfony_debug extension + to track the backtrace of fatal errors + +2.6.0 +----- + +* generalized ErrorHandler and ExceptionHandler, + with some new methods and others deprecated +* enhanced error messages for uncaught exceptions + +2.5.0 +----- + +* added ExceptionHandler::setHandler() +* added UndefinedMethodFatalErrorHandler +* deprecated DummyException + +2.4.0 +----- + + * added a DebugClassLoader able to wrap any autoloader providing a findFile method + * improved error messages for not found classes and functions + +2.3.0 +----- + + * added the component diff --git a/vendor/symfony/debug/Debug.php b/vendor/symfony/debug/Debug.php new file mode 100644 index 00000000..e3665ae5 --- /dev/null +++ b/vendor/symfony/debug/Debug.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\Debug; + +/** + * Registers all the debug tools. + * + * @author Fabien Potencier + */ +class Debug +{ + private static $enabled = false; + + /** + * Enables the debug tools. + * + * This method registers an error handler and an exception handler. + * + * If the Symfony ClassLoader component is available, a special + * class loader is also registered. + * + * @param int $errorReportingLevel The level of error reporting you want + * @param bool $displayErrors Whether to display errors (for development) or just log them (for production) + */ + public static function enable($errorReportingLevel = E_ALL, $displayErrors = true) + { + if (static::$enabled) { + return; + } + + static::$enabled = true; + + if (null !== $errorReportingLevel) { + error_reporting($errorReportingLevel); + } else { + error_reporting(E_ALL); + } + + if ('cli' !== PHP_SAPI) { + ini_set('display_errors', 0); + ExceptionHandler::register(); + } elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) { + // CLI - display errors only if they're not already logged to STDERR + ini_set('display_errors', 1); + } + if ($displayErrors) { + ErrorHandler::register(new ErrorHandler(new BufferingLogger())); + } else { + ErrorHandler::register()->throwAt(0, true); + } + + DebugClassLoader::enable(); + } +} diff --git a/vendor/symfony/debug/DebugClassLoader.php b/vendor/symfony/debug/DebugClassLoader.php new file mode 100644 index 00000000..2e1d7180 --- /dev/null +++ b/vendor/symfony/debug/DebugClassLoader.php @@ -0,0 +1,352 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +/** + * Autoloader checking if the class is really defined in the file found. + * + * The ClassLoader will wrap all registered autoloaders + * and will throw an exception if a file is found but does + * not declare the class. + * + * @author Fabien Potencier + * @author Christophe Coevoet + * @author Nicolas Grekas + */ +class DebugClassLoader +{ + private $classLoader; + private $isFinder; + private static $caseCheck; + private static $final = array(); + private static $finalMethods = array(); + private static $deprecated = array(); + private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null'); + private static $darwinCache = array('/' => array('/', array())); + + /** + * Constructor. + * + * @param callable $classLoader A class loader + */ + public function __construct(callable $classLoader) + { + $this->classLoader = $classLoader; + $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile'); + + if (!isset(self::$caseCheck)) { + $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR); + $i = strrpos($file, DIRECTORY_SEPARATOR); + $dir = substr($file, 0, 1 + $i); + $file = substr($file, 1 + $i); + $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); + $test = realpath($dir.$test); + + if (false === $test || false === $i) { + // filesystem is case sensitive + self::$caseCheck = 0; + } elseif (substr($test, -strlen($file)) === $file) { + // filesystem is case insensitive and realpath() normalizes the case of characters + self::$caseCheck = 1; + } elseif (false !== stripos(PHP_OS, 'darwin')) { + // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters + self::$caseCheck = 2; + } else { + // filesystem case checks failed, fallback to disabling them + self::$caseCheck = 0; + } + } + } + + /** + * Gets the wrapped class loader. + * + * @return callable The wrapped class loader + */ + public function getClassLoader() + { + return $this->classLoader; + } + + /** + * Wraps all autoloaders. + */ + public static function enable() + { + // Ensures we don't hit https://bugs.php.net/42098 + class_exists('Symfony\Component\Debug\ErrorHandler'); + class_exists('Psr\Log\LogLevel'); + + if (!is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (!is_array($function) || !$function[0] instanceof self) { + $function = array(new static($function), 'loadClass'); + } + + spl_autoload_register($function); + } + } + + /** + * Disables the wrapping. + */ + public static function disable() + { + if (!is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (is_array($function) && $function[0] instanceof self) { + $function = $function[0]->getClassLoader(); + } + + spl_autoload_register($function); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + * + * @throws \RuntimeException + */ + public function loadClass($class) + { + ErrorHandler::stackErrors(); + + try { + if ($this->isFinder) { + if ($file = $this->classLoader[0]->findFile($class)) { + require_once $file; + } + } else { + call_user_func($this->classLoader, $class); + $file = false; + } + } finally { + ErrorHandler::unstackErrors(); + } + + $exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); + + if ($class && '\\' === $class[0]) { + $class = substr($class, 1); + } + + if ($exists) { + $refl = new \ReflectionClass($class); + $name = $refl->getName(); + + if ($name !== $class && 0 === strcasecmp($name, $class)) { + throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); + } + + $parent = get_parent_class($class); + + // Not an interface nor a trait + if (class_exists($name, false)) { + if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { + self::$final[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; + } + + if ($parent && isset(self::$final[$parent])) { + @trigger_error(sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $name), E_USER_DEPRECATED); + } + + // Inherit @final annotations + self::$finalMethods[$name] = $parent && isset(self::$finalMethods[$parent]) ? self::$finalMethods[$parent] : array(); + + foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { + if ($method->class !== $name) { + continue; + } + + if ($parent && isset(self::$finalMethods[$parent][$method->name])) { + @trigger_error(sprintf('%s It may change without further notice as of its next major version. You should not extend it from "%s".', self::$finalMethods[$parent][$method->name], $name), E_USER_DEPRECATED); + } + + $doc = $method->getDocComment(); + if (false === $doc || false === strpos($doc, '@final')) { + continue; + } + + if (preg_match('#\n\s+\* @final(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) { + $message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; + self::$finalMethods[$name][$method->name] = sprintf('The "%s::%s()" method is considered final%s.', $name, $method->name, $message); + } + } + } + + if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { + @trigger_error(sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED); + } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { + self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]); + } else { + // Don't trigger deprecations for classes in the same vendor + if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) { + $len = 0; + $ns = ''; + } else { + switch ($ns = substr($name, 0, $len)) { + case 'Symfony\Bridge\\': + case 'Symfony\Bundle\\': + case 'Symfony\Component\\': + $ns = 'Symfony\\'; + $len = strlen($ns); + break; + } + } + + if (!$parent || strncmp($ns, $parent, $len)) { + if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { + @trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); + } + + $parentInterfaces = array(); + $deprecatedInterfaces = array(); + if ($parent) { + foreach (class_implements($parent) as $interface) { + $parentInterfaces[$interface] = 1; + } + } + + foreach ($refl->getInterfaceNames() as $interface) { + if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) { + $deprecatedInterfaces[] = $interface; + } + foreach (class_implements($interface) as $interface) { + $parentInterfaces[$interface] = 1; + } + } + + foreach ($deprecatedInterfaces as $interface) { + if (!isset($parentInterfaces[$interface])) { + @trigger_error(sprintf('The "%s" %s "%s" that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED); + } + } + } + } + } + + if ($file) { + if (!$exists) { + if (false !== strpos($class, '/')) { + throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); + } + + throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); + } + if (self::$caseCheck) { + $real = explode('\\', $class.strrchr($file, '.')); + $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file)); + + $i = count($tail) - 1; + $j = count($real) - 1; + + while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { + --$i; + --$j; + } + + array_splice($tail, 0, $i + 1); + } + if (self::$caseCheck && $tail) { + $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail); + $tailLen = strlen($tail); + $real = $refl->getFileName(); + + if (2 === self::$caseCheck) { + // realpath() on MacOSX doesn't normalize the case of characters + + $i = 1 + strrpos($real, '/'); + $file = substr($real, $i); + $real = substr($real, 0, $i); + + if (isset(self::$darwinCache[$real])) { + $kDir = $real; + } else { + $kDir = strtolower($real); + + if (isset(self::$darwinCache[$kDir])) { + $real = self::$darwinCache[$kDir][0]; + } else { + $dir = getcwd(); + chdir($real); + $real = getcwd().'/'; + chdir($dir); + + $dir = $real; + $k = $kDir; + $i = strlen($dir) - 1; + while (!isset(self::$darwinCache[$k])) { + self::$darwinCache[$k] = array($dir, array()); + self::$darwinCache[$dir] = &self::$darwinCache[$k]; + + while ('/' !== $dir[--$i]) { + } + $k = substr($k, 0, ++$i); + $dir = substr($dir, 0, $i--); + } + } + } + + $dirFiles = self::$darwinCache[$kDir][1]; + + if (isset($dirFiles[$file])) { + $kFile = $file; + } else { + $kFile = strtolower($file); + + if (!isset($dirFiles[$kFile])) { + foreach (scandir($real, 2) as $f) { + if ('.' !== $f[0]) { + $dirFiles[$f] = $f; + if ($f === $file) { + $kFile = $k = $file; + } elseif ($f !== $k = strtolower($f)) { + $dirFiles[$k] = $f; + } + } + } + self::$darwinCache[$kDir][1] = $dirFiles; + } + } + + $real .= $dirFiles[$kFile]; + } + + if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) + && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) + ) { + throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1))); + } + } + + return true; + } + } +} diff --git a/vendor/symfony/debug/ErrorHandler.php b/vendor/symfony/debug/ErrorHandler.php new file mode 100644 index 00000000..d6c4d10d --- /dev/null +++ b/vendor/symfony/debug/ErrorHandler.php @@ -0,0 +1,690 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Psr\Log\LogLevel; +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\Exception\ContextErrorException; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\FatalThrowableError; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface; + +/** + * A generic ErrorHandler for the PHP engine. + * + * Provides five bit fields that control how errors are handled: + * - thrownErrors: errors thrown as \ErrorException + * - loggedErrors: logged errors, when not @-silenced + * - scopedErrors: errors thrown or logged with their local context + * - tracedErrors: errors logged with their stack trace + * - screamedErrors: never @-silenced errors + * + * Each error level can be logged by a dedicated PSR-3 logger object. + * Screaming only applies to logging. + * Throwing takes precedence over logging. + * Uncaught exceptions are logged as E_ERROR. + * E_DEPRECATED and E_USER_DEPRECATED levels never throw. + * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. + * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. + * As errors have a performance cost, repeated errors are all logged, so that the developer + * can see them and weight them as more important to fix than others of the same level. + * + * @author Nicolas Grekas + * @author Grégoire Pineau + */ +class ErrorHandler +{ + private $levels = array( + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + E_NOTICE => 'Notice', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_WARNING => 'Warning', + E_USER_WARNING => 'User Warning', + E_COMPILE_WARNING => 'Compile Warning', + E_CORE_WARNING => 'Core Warning', + E_USER_ERROR => 'User Error', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + E_COMPILE_ERROR => 'Compile Error', + E_PARSE => 'Parse Error', + E_ERROR => 'Error', + E_CORE_ERROR => 'Core Error', + ); + + private $loggers = array( + E_DEPRECATED => array(null, LogLevel::INFO), + E_USER_DEPRECATED => array(null, LogLevel::INFO), + E_NOTICE => array(null, LogLevel::WARNING), + E_USER_NOTICE => array(null, LogLevel::WARNING), + E_STRICT => array(null, LogLevel::WARNING), + E_WARNING => array(null, LogLevel::WARNING), + E_USER_WARNING => array(null, LogLevel::WARNING), + E_COMPILE_WARNING => array(null, LogLevel::WARNING), + E_CORE_WARNING => array(null, LogLevel::WARNING), + E_USER_ERROR => array(null, LogLevel::CRITICAL), + E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL), + E_COMPILE_ERROR => array(null, LogLevel::CRITICAL), + E_PARSE => array(null, LogLevel::CRITICAL), + E_ERROR => array(null, LogLevel::CRITICAL), + E_CORE_ERROR => array(null, LogLevel::CRITICAL), + ); + + private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE + private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE + private $loggedErrors = 0; + private $traceReflector; + + private $isRecursive = 0; + private $isRoot = false; + private $exceptionHandler; + private $bootstrappingLogger; + + private static $reservedMemory; + private static $stackedErrors = array(); + private static $stackedErrorLevels = array(); + private static $toStringException = null; + private static $exitCode = 0; + + /** + * Registers the error handler. + * + * @param self|null $handler The handler to register + * @param bool $replace Whether to replace or not any existing handler + * + * @return self The registered error handler + */ + public static function register(self $handler = null, $replace = true) + { + if (null === self::$reservedMemory) { + self::$reservedMemory = str_repeat('x', 10240); + register_shutdown_function(__CLASS__.'::handleFatalError'); + } + + if ($handlerIsNew = null === $handler) { + $handler = new static(); + } + + if (null === $prev = set_error_handler(array($handler, 'handleError'))) { + restore_error_handler(); + // Specifying the error types earlier would expose us to https://bugs.php.net/63206 + set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors); + $handler->isRoot = true; + } + + if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) { + $handler = $prev[0]; + $replace = false; + } + if ($replace || !$prev) { + $handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException'))); + } else { + restore_error_handler(); + } + + $handler->throwAt(E_ALL & $handler->thrownErrors, true); + + return $handler; + } + + public function __construct(BufferingLogger $bootstrappingLogger = null) + { + if ($bootstrappingLogger) { + $this->bootstrappingLogger = $bootstrappingLogger; + $this->setDefaultLogger($bootstrappingLogger); + } + $this->traceReflector = new \ReflectionProperty('Exception', 'trace'); + $this->traceReflector->setAccessible(true); + } + + /** + * Sets a logger to non assigned errors levels. + * + * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param bool $replace Whether to replace or not any existing logger + */ + public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false) + { + $loggers = array(); + + if (is_array($levels)) { + foreach ($levels as $type => $logLevel) { + if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { + $loggers[$type] = array($logger, $logLevel); + } + } + } else { + if (null === $levels) { + $levels = E_ALL; + } + foreach ($this->loggers as $type => $log) { + if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { + $log[0] = $logger; + $loggers[$type] = $log; + } + } + } + + $this->setLoggers($loggers); + } + + /** + * Sets a logger for each error level. + * + * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map + * + * @return array The previous map + * + * @throws \InvalidArgumentException + */ + public function setLoggers(array $loggers) + { + $prevLogged = $this->loggedErrors; + $prev = $this->loggers; + $flush = array(); + + foreach ($loggers as $type => $log) { + if (!isset($prev[$type])) { + throw new \InvalidArgumentException('Unknown error type: '.$type); + } + if (!is_array($log)) { + $log = array($log); + } elseif (!array_key_exists(0, $log)) { + throw new \InvalidArgumentException('No logger provided'); + } + if (null === $log[0]) { + $this->loggedErrors &= ~$type; + } elseif ($log[0] instanceof LoggerInterface) { + $this->loggedErrors |= $type; + } else { + throw new \InvalidArgumentException('Invalid logger provided'); + } + $this->loggers[$type] = $log + $prev[$type]; + + if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { + $flush[$type] = $type; + } + } + $this->reRegister($prevLogged | $this->thrownErrors); + + if ($flush) { + foreach ($this->bootstrappingLogger->cleanLogs() as $log) { + $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR; + if (!isset($flush[$type])) { + $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); + } elseif ($this->loggers[$type][0]) { + $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); + } + } + } + + return $prev; + } + + /** + * Sets a user exception handler. + * + * @param callable $handler A handler that will be called on Exception + * + * @return callable|null The previous exception handler + */ + public function setExceptionHandler(callable $handler = null) + { + $prev = $this->exceptionHandler; + $this->exceptionHandler = $handler; + + return $prev; + } + + /** + * Sets the PHP error levels that throw an exception when a PHP error occurs. + * + * @param int $levels A bit field of E_* constants for thrown errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function throwAt($levels, $replace = false) + { + $prev = $this->thrownErrors; + $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED; + if (!$replace) { + $this->thrownErrors |= $prev; + } + $this->reRegister($prev | $this->loggedErrors); + + return $prev; + } + + /** + * Sets the PHP error levels for which local variables are preserved. + * + * @param int $levels A bit field of E_* constants for scoped errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function scopeAt($levels, $replace = false) + { + $prev = $this->scopedErrors; + $this->scopedErrors = (int) $levels; + if (!$replace) { + $this->scopedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the PHP error levels for which the stack trace is preserved. + * + * @param int $levels A bit field of E_* constants for traced errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function traceAt($levels, $replace = false) + { + $prev = $this->tracedErrors; + $this->tracedErrors = (int) $levels; + if (!$replace) { + $this->tracedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the error levels where the @-operator is ignored. + * + * @param int $levels A bit field of E_* constants for screamed errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function screamAt($levels, $replace = false) + { + $prev = $this->screamedErrors; + $this->screamedErrors = (int) $levels; + if (!$replace) { + $this->screamedErrors |= $prev; + } + + return $prev; + } + + /** + * Re-registers as a PHP error handler if levels changed. + */ + private function reRegister($prev) + { + if ($prev !== $this->thrownErrors | $this->loggedErrors) { + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler === $this) { + restore_error_handler(); + if ($this->isRoot) { + set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors); + } else { + set_error_handler(array($this, 'handleError')); + } + } + } + } + + /** + * Handles errors by filtering then logging them according to the configured bit fields. + * + * @param int $type One of the E_* constants + * @param string $message + * @param string $file + * @param int $line + * + * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself + * + * @throws \ErrorException When $this->thrownErrors requests so + * + * @internal + */ + public function handleError($type, $message, $file, $line) + { + // Level is the current error reporting level to manage silent error. + // Strong errors are not authorized to be silenced. + $level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; + $log = $this->loggedErrors & $type; + $throw = $this->thrownErrors & $type & $level; + $type &= $level | $this->screamedErrors; + + if (!$type || (!$log && !$throw)) { + return $type && $log; + } + $scope = $this->scopedErrors & $type; + + if (4 < $numArgs = func_num_args()) { + $context = $scope ? (func_get_arg(4) ?: array()) : array(); + $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM + } else { + $context = array(); + $backtrace = null; + } + + if (isset($context['GLOBALS']) && $scope) { + $e = $context; // Whatever the signature of the method, + unset($e['GLOBALS'], $context); // $context is always a reference in 5.3 + $context = $e; + } + + if (null !== $backtrace && $type & E_ERROR) { + // E_ERROR fatal errors are triggered on HHVM when + // hhvm.error_handling.call_user_handler_on_fatals=1 + // which is the way to get their backtrace. + $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace')); + + return true; + } + + $logMessage = $this->levels[$type].': '.$message; + + if (null !== self::$toStringException) { + $errorAsException = self::$toStringException; + self::$toStringException = null; + } elseif (!$throw && !($type & $level)) { + $errorAsException = new SilencedErrorContext($type, $file, $line); + } else { + if ($scope) { + $errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context); + } else { + $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); + } + + // Clean the trace by removing function arguments and the first frames added by the error handler itself. + if ($throw || $this->tracedErrors & $type) { + $backtrace = $backtrace ?: $errorAsException->getTrace(); + $lightTrace = $backtrace; + + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $lightTrace = array_slice($lightTrace, 1 + $i); + break; + } + } + if (!($throw || $this->scopedErrors & $type)) { + for ($i = 0; isset($lightTrace[$i]); ++$i) { + unset($lightTrace[$i]['args']); + } + } + $this->traceReflector->setValue($errorAsException, $lightTrace); + } else { + $this->traceReflector->setValue($errorAsException, array()); + } + } + + if ($throw) { + if (E_USER_ERROR & $type) { + for ($i = 1; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) + && '__toString' === $backtrace[$i]['function'] + && '->' === $backtrace[$i]['type'] + && !isset($backtrace[$i - 1]['class']) + && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) + ) { + // Here, we know trigger_error() has been called from __toString(). + // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead. + // A small convention allows working around the limitation: + // given a caught $e exception in __toString(), quitting the method with + // `return trigger_error($e, E_USER_ERROR);` allows this error handler + // to make $e get through the __toString() barrier. + + foreach ($context as $e) { + if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) { + if (1 === $i) { + // On HHVM + $errorAsException = $e; + break; + } + self::$toStringException = $e; + + return true; + } + } + + if (1 < $i) { + // On PHP (not on HHVM), display the original error message instead of the default one. + $this->handleException($errorAsException); + + // Stop the process by giving back the error to the native handler. + return false; + } + } + } + } + + throw $errorAsException; + } + + if ($this->isRecursive) { + $log = 0; + } elseif (self::$stackedErrorLevels) { + self::$stackedErrors[] = array( + $this->loggers[$type][0], + ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, + $logMessage, + array('exception' => $errorAsException), + ); + } else { + try { + $this->isRecursive = true; + $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; + $this->loggers[$type][0]->log($level, $logMessage, array('exception' => $errorAsException)); + } finally { + $this->isRecursive = false; + } + } + + return $type && $log; + } + + /** + * Handles an exception by logging then forwarding it to another handler. + * + * @param \Exception|\Throwable $exception An exception to handle + * @param array $error An array as returned by error_get_last() + * + * @internal + */ + public function handleException($exception, array $error = null) + { + if (null === $error) { + self::$exitCode = 255; + } + if (!$exception instanceof \Exception) { + $exception = new FatalThrowableError($exception); + } + $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; + + if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { + if ($exception instanceof FatalErrorException) { + if ($exception instanceof FatalThrowableError) { + $error = array( + 'type' => $type, + 'message' => $message = $exception->getMessage(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + ); + } else { + $message = 'Fatal '.$exception->getMessage(); + } + } elseif ($exception instanceof \ErrorException) { + $message = 'Uncaught '.$exception->getMessage(); + } else { + $message = 'Uncaught Exception: '.$exception->getMessage(); + } + } + if ($this->loggedErrors & $type) { + try { + $this->loggers[$type][0]->log($this->loggers[$type][1], $message, array('exception' => $exception)); + } catch (\Exception $handlerException) { + } catch (\Throwable $handlerException) { + } + } + if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { + foreach ($this->getFatalErrorHandlers() as $handler) { + if ($e = $handler->handleError($error, $exception)) { + $exception = $e; + break; + } + } + } + if (empty($this->exceptionHandler)) { + throw $exception; // Give back $exception to the native handler + } + try { + call_user_func($this->exceptionHandler, $exception); + } catch (\Exception $handlerException) { + } catch (\Throwable $handlerException) { + } + if (isset($handlerException)) { + $this->exceptionHandler = null; + $this->handleException($handlerException); + } + } + + /** + * Shutdown registered function for handling PHP fatal errors. + * + * @param array $error An array as returned by error_get_last() + * + * @internal + */ + public static function handleFatalError(array $error = null) + { + if (null === self::$reservedMemory) { + return; + } + + self::$reservedMemory = null; + + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + + if (!$handler instanceof self) { + return; + } + + if ($exit = null === $error) { + $error = error_get_last(); + } + + try { + while (self::$stackedErrorLevels) { + static::unstackErrors(); + } + } catch (\Exception $exception) { + // Handled below + } catch (\Throwable $exception) { + // Handled below + } + + if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) { + // Let's not throw anymore but keep logging + $handler->throwAt(0, true); + $trace = isset($error['backtrace']) ? $error['backtrace'] : null; + + if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { + $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace); + } else { + $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace); + } + } + + try { + if (isset($exception)) { + self::$exitCode = 255; + $handler->handleException($exception, $error); + } + } catch (FatalErrorException $e) { + // Ignore this re-throw + } + + if ($exit && self::$exitCode) { + $exitCode = self::$exitCode; + register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); + } + } + + /** + * Configures the error handler for delayed handling. + * Ensures also that non-catchable fatal errors are never silenced. + * + * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724 + * PHP has a compile stage where it behaves unusually. To workaround it, + * we plug an error handler that only stacks errors for later. + * + * The most important feature of this is to prevent + * autoloading until unstackErrors() is called. + */ + public static function stackErrors() + { + self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); + } + + /** + * Unstacks stacked errors and forwards to the logger. + */ + public static function unstackErrors() + { + $level = array_pop(self::$stackedErrorLevels); + + if (null !== $level) { + $errorReportingLevel = error_reporting($level); + if ($errorReportingLevel !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) { + // If the user changed the error level, do not overwrite it + error_reporting($errorReportingLevel); + } + } + + if (empty(self::$stackedErrorLevels)) { + $errors = self::$stackedErrors; + self::$stackedErrors = array(); + + foreach ($errors as $error) { + $error[0]->log($error[1], $error[2], $error[3]); + } + } + } + + /** + * Gets the fatal error handlers. + * + * Override this method if you want to define more fatal error handlers. + * + * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface + */ + protected function getFatalErrorHandlers() + { + return array( + new UndefinedFunctionFatalErrorHandler(), + new UndefinedMethodFatalErrorHandler(), + new ClassNotFoundFatalErrorHandler(), + ); + } +} diff --git a/vendor/symfony/debug/Exception/ClassNotFoundException.php b/vendor/symfony/debug/Exception/ClassNotFoundException.php new file mode 100644 index 00000000..b91bf466 --- /dev/null +++ b/vendor/symfony/debug/Exception/ClassNotFoundException.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\Debug\Exception; + +/** + * Class (or Trait or Interface) Not Found Exception. + * + * @author Konstanton Myakshin + */ +class ClassNotFoundException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/vendor/symfony/debug/Exception/ContextErrorException.php b/vendor/symfony/debug/Exception/ContextErrorException.php new file mode 100644 index 00000000..6561d4df --- /dev/null +++ b/vendor/symfony/debug/Exception/ContextErrorException.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Error Exception with Variable Context. + * + * @author Christian Sciberras + * + * @deprecated since version 3.3. Instead, \ErrorException will be used directly in 4.0. + */ +class ContextErrorException extends \ErrorException +{ + private $context = array(); + + public function __construct($message, $code, $severity, $filename, $lineno, $context = array()) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + $this->context = $context; + } + + /** + * @return array Array of variables that existed when the exception occurred + */ + public function getContext() + { + @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); + + return $this->context; + } +} diff --git a/vendor/symfony/debug/Exception/FatalErrorException.php b/vendor/symfony/debug/Exception/FatalErrorException.php new file mode 100644 index 00000000..f24a54e7 --- /dev/null +++ b/vendor/symfony/debug/Exception/FatalErrorException.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Fatal Error Exception. + * + * @author Konstanton Myakshin + */ +class FatalErrorException extends \ErrorException +{ + public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + + if (null !== $trace) { + if (!$traceArgs) { + foreach ($trace as &$frame) { + unset($frame['args'], $frame['this'], $frame); + } + } + + $this->setTrace($trace); + } elseif (null !== $traceOffset) { + if (function_exists('xdebug_get_function_stack')) { + $trace = xdebug_get_function_stack(); + if (0 < $traceOffset) { + array_splice($trace, -$traceOffset); + } + + foreach ($trace as &$frame) { + if (!isset($frame['type'])) { + // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 + if (isset($frame['class'])) { + $frame['type'] = '::'; + } + } elseif ('dynamic' === $frame['type']) { + $frame['type'] = '->'; + } elseif ('static' === $frame['type']) { + $frame['type'] = '::'; + } + + // XDebug also has a different name for the parameters array + if (!$traceArgs) { + unset($frame['params'], $frame['args']); + } elseif (isset($frame['params']) && !isset($frame['args'])) { + $frame['args'] = $frame['params']; + unset($frame['params']); + } + } + + unset($frame); + $trace = array_reverse($trace); + } elseif (function_exists('symfony_debug_backtrace')) { + $trace = symfony_debug_backtrace(); + if (0 < $traceOffset) { + array_splice($trace, 0, $traceOffset); + } + } else { + $trace = array(); + } + + $this->setTrace($trace); + } + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/vendor/symfony/debug/Exception/FatalThrowableError.php b/vendor/symfony/debug/Exception/FatalThrowableError.php new file mode 100644 index 00000000..34f43b17 --- /dev/null +++ b/vendor/symfony/debug/Exception/FatalThrowableError.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Fatal Throwable Error. + * + * @author Nicolas Grekas + */ +class FatalThrowableError extends FatalErrorException +{ + public function __construct(\Throwable $e) + { + if ($e instanceof \ParseError) { + $message = 'Parse error: '.$e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: '.$e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = $e->getMessage(); + $severity = E_ERROR; + } + + \ErrorException::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + $this->setTrace($e->getTrace()); + } +} diff --git a/vendor/symfony/debug/Exception/FlattenException.php b/vendor/symfony/debug/Exception/FlattenException.php new file mode 100644 index 00000000..24679dca --- /dev/null +++ b/vendor/symfony/debug/Exception/FlattenException.php @@ -0,0 +1,263 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; + +/** + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + */ +class FlattenException +{ + private $message; + private $code; + private $previous; + private $trace; + private $class; + private $statusCode; + private $headers; + private $file; + private $line; + + public static function create(\Exception $exception, $statusCode = null, array $headers = array()) + { + $e = new static(); + $e->setMessage($exception->getMessage()); + $e->setCode($exception->getCode()); + + if ($exception instanceof HttpExceptionInterface) { + $statusCode = $exception->getStatusCode(); + $headers = array_merge($headers, $exception->getHeaders()); + } elseif ($exception instanceof RequestExceptionInterface) { + $statusCode = 400; + } + + if (null === $statusCode) { + $statusCode = 500; + } + + $e->setStatusCode($statusCode); + $e->setHeaders($headers); + $e->setTraceFromException($exception); + $e->setClass(get_class($exception)); + $e->setFile($exception->getFile()); + $e->setLine($exception->getLine()); + + $previous = $exception->getPrevious(); + + if ($previous instanceof \Exception) { + $e->setPrevious(static::create($previous)); + } elseif ($previous instanceof \Throwable) { + $e->setPrevious(static::create(new FatalThrowableError($previous))); + } + + return $e; + } + + public function toArray() + { + $exceptions = array(); + foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) { + $exceptions[] = array( + 'message' => $exception->getMessage(), + 'class' => $exception->getClass(), + 'trace' => $exception->getTrace(), + ); + } + + return $exceptions; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function setStatusCode($code) + { + $this->statusCode = $code; + } + + public function getHeaders() + { + return $this->headers; + } + + public function setHeaders(array $headers) + { + $this->headers = $headers; + } + + public function getClass() + { + return $this->class; + } + + public function setClass($class) + { + $this->class = $class; + } + + public function getFile() + { + return $this->file; + } + + public function setFile($file) + { + $this->file = $file; + } + + public function getLine() + { + return $this->line; + } + + public function setLine($line) + { + $this->line = $line; + } + + public function getMessage() + { + return $this->message; + } + + public function setMessage($message) + { + $this->message = $message; + } + + public function getCode() + { + return $this->code; + } + + public function setCode($code) + { + $this->code = $code; + } + + public function getPrevious() + { + return $this->previous; + } + + public function setPrevious(FlattenException $previous) + { + $this->previous = $previous; + } + + public function getAllPrevious() + { + $exceptions = array(); + $e = $this; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + return $exceptions; + } + + public function getTrace() + { + return $this->trace; + } + + public function setTraceFromException(\Exception $exception) + { + $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); + } + + public function setTrace($trace, $file, $line) + { + $this->trace = array(); + $this->trace[] = array( + 'namespace' => '', + 'short_class' => '', + 'class' => '', + 'type' => '', + 'function' => '', + 'file' => $file, + 'line' => $line, + 'args' => array(), + ); + foreach ($trace as $entry) { + $class = ''; + $namespace = ''; + if (isset($entry['class'])) { + $parts = explode('\\', $entry['class']); + $class = array_pop($parts); + $namespace = implode('\\', $parts); + } + + $this->trace[] = array( + 'namespace' => $namespace, + 'short_class' => $class, + 'class' => isset($entry['class']) ? $entry['class'] : '', + 'type' => isset($entry['type']) ? $entry['type'] : '', + 'function' => isset($entry['function']) ? $entry['function'] : null, + 'file' => isset($entry['file']) ? $entry['file'] : null, + 'line' => isset($entry['line']) ? $entry['line'] : null, + 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(), + ); + } + } + + private function flattenArgs($args, $level = 0, &$count = 0) + { + $result = array(); + foreach ($args as $key => $value) { + if (++$count > 1e4) { + return array('array', '*SKIPPED over 10000 entries*'); + } + if ($value instanceof \__PHP_Incomplete_Class) { + // is_object() returns false on PHP<=7.1 + $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value)); + } elseif (is_object($value)) { + $result[$key] = array('object', get_class($value)); + } elseif (is_array($value)) { + if ($level > 10) { + $result[$key] = array('array', '*DEEP NESTED ARRAY*'); + } else { + $result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count)); + } + } elseif (null === $value) { + $result[$key] = array('null', null); + } elseif (is_bool($value)) { + $result[$key] = array('boolean', $value); + } elseif (is_int($value)) { + $result[$key] = array('integer', $value); + } elseif (is_float($value)) { + $result[$key] = array('float', $value); + } elseif (is_resource($value)) { + $result[$key] = array('resource', get_resource_type($value)); + } else { + $result[$key] = array('string', (string) $value); + } + } + + return $result; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/vendor/symfony/debug/Exception/OutOfMemoryException.php b/vendor/symfony/debug/Exception/OutOfMemoryException.php new file mode 100644 index 00000000..fec19798 --- /dev/null +++ b/vendor/symfony/debug/Exception/OutOfMemoryException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Out of memory exception. + * + * @author Nicolas Grekas + */ +class OutOfMemoryException extends FatalErrorException +{ +} diff --git a/vendor/symfony/debug/Exception/SilencedErrorContext.php b/vendor/symfony/debug/Exception/SilencedErrorContext.php new file mode 100644 index 00000000..0c3a0e1d --- /dev/null +++ b/vendor/symfony/debug/Exception/SilencedErrorContext.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Data Object that represents a Silenced Error. + * + * @author Grégoire Pineau + */ +class SilencedErrorContext implements \JsonSerializable +{ + private $severity; + private $file; + private $line; + + public function __construct($severity, $file, $line) + { + $this->severity = $severity; + $this->file = $file; + $this->line = $line; + } + + public function getSeverity() + { + return $this->severity; + } + + public function getFile() + { + return $this->file; + } + + public function getLine() + { + return $this->line; + } + + public function JsonSerialize() + { + return array( + 'severity' => $this->severity, + 'file' => $this->file, + 'line' => $this->line, + ); + } +} diff --git a/vendor/symfony/debug/Exception/UndefinedFunctionException.php b/vendor/symfony/debug/Exception/UndefinedFunctionException.php new file mode 100644 index 00000000..a66ae2a3 --- /dev/null +++ b/vendor/symfony/debug/Exception/UndefinedFunctionException.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\Debug\Exception; + +/** + * Undefined Function Exception. + * + * @author Konstanton Myakshin + */ +class UndefinedFunctionException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/vendor/symfony/debug/Exception/UndefinedMethodException.php b/vendor/symfony/debug/Exception/UndefinedMethodException.php new file mode 100644 index 00000000..350dc318 --- /dev/null +++ b/vendor/symfony/debug/Exception/UndefinedMethodException.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\Debug\Exception; + +/** + * Undefined Method Exception. + * + * @author Grégoire Pineau + */ +class UndefinedMethodException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/vendor/symfony/debug/ExceptionHandler.php b/vendor/symfony/debug/ExceptionHandler.php new file mode 100644 index 00000000..5c399ef0 --- /dev/null +++ b/vendor/symfony/debug/ExceptionHandler.php @@ -0,0 +1,414 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; + +/** + * ExceptionHandler converts an exception to a Response object. + * + * It is mostly useful in debug mode to replace the default PHP/XDebug + * output with something prettier and more useful. + * + * As this class is mainly used during Kernel boot, where nothing is yet + * available, the Response content is always HTML. + * + * @author Fabien Potencier + * @author Nicolas Grekas + */ +class ExceptionHandler +{ + private $debug; + private $charset; + private $handler; + private $caughtBuffer; + private $caughtLength; + private $fileLinkFormat; + + public function __construct($debug = true, $charset = null, $fileLinkFormat = null) + { + $this->debug = $debug; + $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + } + + /** + * Registers the exception handler. + * + * @param bool $debug Enable/disable debug mode, where the stack trace is displayed + * @param string|null $charset The charset used by exception messages + * @param string|null $fileLinkFormat The IDE link template + * + * @return static + */ + public static function register($debug = true, $charset = null, $fileLinkFormat = null) + { + $handler = new static($debug, $charset, $fileLinkFormat); + + $prev = set_exception_handler(array($handler, 'handle')); + if (is_array($prev) && $prev[0] instanceof ErrorHandler) { + restore_exception_handler(); + $prev[0]->setExceptionHandler(array($handler, 'handle')); + } + + return $handler; + } + + /** + * Sets a user exception handler. + * + * @param callable $handler An handler that will be called on Exception + * + * @return callable|null The previous exception handler if any + */ + public function setHandler(callable $handler = null) + { + $old = $this->handler; + $this->handler = $handler; + + return $old; + } + + /** + * Sets the format for links to source files. + * + * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files + * + * @return string The previous file link format + */ + public function setFileLinkFormat($fileLinkFormat) + { + $old = $this->fileLinkFormat; + $this->fileLinkFormat = $fileLinkFormat; + + return $old; + } + + /** + * Sends a response for the given Exception. + * + * To be as fail-safe as possible, the exception is first handled + * by our simple exception handler, then by the user exception handler. + * The latter takes precedence and any output from the former is cancelled, + * if and only if nothing bad happens in this handling path. + */ + public function handle(\Exception $exception) + { + if (null === $this->handler || $exception instanceof OutOfMemoryException) { + $this->sendPhpResponse($exception); + + return; + } + + $caughtLength = $this->caughtLength = 0; + + ob_start(function ($buffer) { + $this->caughtBuffer = $buffer; + + return ''; + }); + + $this->sendPhpResponse($exception); + while (null === $this->caughtBuffer && ob_end_flush()) { + // Empty loop, everything is in the condition + } + if (isset($this->caughtBuffer[0])) { + ob_start(function ($buffer) { + if ($this->caughtLength) { + // use substr_replace() instead of substr() for mbstring overloading resistance + $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); + if (isset($cleanBuffer[0])) { + $buffer = $cleanBuffer; + } + } + + return $buffer; + }); + + echo $this->caughtBuffer; + $caughtLength = ob_get_length(); + } + $this->caughtBuffer = null; + + try { + call_user_func($this->handler, $exception); + $this->caughtLength = $caughtLength; + } catch (\Exception $e) { + if (!$caughtLength) { + // All handlers failed. Let PHP handle that now. + throw $exception; + } + } + } + + /** + * Sends the error associated with the given Exception as a plain PHP response. + * + * This method uses plain PHP functions like header() and echo to output + * the response. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + */ + public function sendPhpResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + if (!headers_sent()) { + header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); + foreach ($exception->getHeaders() as $name => $value) { + header($name.': '.$value, false); + } + header('Content-Type: text/html; charset='.$this->charset); + } + + echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); + } + + /** + * Gets the full HTML content associated with the given exception. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + * + * @return string The HTML content as a string + */ + public function getHtml($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + return $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); + } + + /** + * Gets the HTML content associated with the given exception. + * + * @param FlattenException $exception A FlattenException instance + * + * @return string The content as a string + */ + public function getContent(FlattenException $exception) + { + switch ($exception->getStatusCode()) { + case 404: + $title = 'Sorry, the page you are looking for could not be found.'; + break; + default: + $title = 'Whoops, looks like something went wrong.'; + } + + $content = ''; + if ($this->debug) { + try { + $count = count($exception->getAllPrevious()); + $total = $count + 1; + foreach ($exception->toArray() as $position => $e) { + $ind = $count - $position + 1; + $class = $this->formatClass($e['class']); + $message = nl2br($this->escapeHtml($e['message'])); + $content .= sprintf(<<<'EOF' +
+ + + +EOF + , $ind, $total, $class, $message); + foreach ($e['trace'] as $trace) { + $content .= '\n"; + } + + $content .= "\n
+

+ (%d/%d) + %s +

+

%s

+
'; + if ($trace['function']) { + $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); + } + if (isset($trace['file']) && isset($trace['line'])) { + $content .= $this->formatPath($trace['file'], $trace['line']); + } + $content .= "
\n
\n"; + } + } catch (\Exception $e) { + // something nasty happened and we cannot throw an exception anymore + if ($this->debug) { + $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $this->escapeHtml($e->getMessage())); + } else { + $title = 'Whoops, looks like something went wrong.'; + } + } + } + + $symfonyGhostImageContents = $this->getSymfonyGhostAsSvg(); + + return << +
+
+

$title

+
$symfonyGhostImageContents
+
+
+ + +
+ $content +
+EOF; + } + + /** + * Gets the stylesheet associated with the given exception. + * + * @param FlattenException $exception A FlattenException instance + * + * @return string The stylesheet as a string + */ + public function getStylesheet(FlattenException $exception) + { + return <<<'EOF' + body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; margin: 0; padding-bottom: 45px; } + + a { cursor: pointer; text-decoration: none; } + a:hover { text-decoration: underline; } + abbr[title] { border-bottom: none; cursor: help; text-decoration: none; } + + code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; } + + table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; } + table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; } + table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; } + table th { background-color: #E0E0E0; font-weight: bold; text-align: left; } + + .hidden-xs-down { display: none; } + .block { display: block; } + .break-long-words { -ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; } + .text-muted { color: #999; } + + .container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } + .container::after { content: ""; display: table; clear: both; } + + .exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 30px; } + + .exception-message-wrapper { display: flex; align-items: center; min-height: 70px; } + .exception-message { flex-grow: 1; padding: 30px 0; } + .exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } + .exception-message.long { font-size: 18px; } + .exception-message a { text-decoration: none; } + .exception-message a:hover { text-decoration: underline; } + + .exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } + + .trace + .trace { margin-top: 30px; } + .trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } + + .trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } + + .trace-file-path, .trace-file-path a { margin-top: 3px; color: #999; color: #795da3; color: #B0413E; color: #222; font-size: 13px; } + .trace-class { color: #B0413E; } + .trace-type { padding: 0 2px; } + .trace-method { color: #B0413E; color: #222; font-weight: bold; color: #B0413E; } + .trace-arguments { color: #222; color: #999; font-weight: normal; color: #795da3; color: #777; padding-left: 2px; } + + @media (min-width: 575px) { + .hidden-xs-down { display: initial; } + } +EOF; + } + + private function decorate($content, $css) + { + return << + + + + + + + + $content + + +EOF; + } + + private function formatClass($class) + { + $parts = explode('\\', $class); + + return sprintf('%s', $class, array_pop($parts)); + } + + private function formatPath($path, $line) + { + $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); + $fmt = $this->fileLinkFormat; + + if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $path, '%l' => $line)) : $fmt->format($path, $line)) { + return sprintf('in %s (line %d)', $this->escapeHtml($link), $file, $line); + } + + return sprintf('in %s (line %d)', $this->escapeHtml($path), $file, $line); + } + + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ + private function formatArgs(array $args) + { + $result = array(); + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf('object(%s)', $this->formatClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf('array(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true))); + } + + $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); + } + + return implode(', ', $result); + } + + /** + * HTML-encodes a string. + */ + private function escapeHtml($str) + { + return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset); + } + + private function getSymfonyGhostAsSvg() + { + return ''; + } +} diff --git a/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php b/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php new file mode 100644 index 00000000..32ba9a09 --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\ClassNotFoundException; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\DebugClassLoader; +use Composer\Autoload\ClassLoader as ComposerClassLoader; +use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader; + +/** + * ErrorHandler for classes that do not exist. + * + * @author Fabien Potencier + */ +class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + $messageLen = strlen($error['message']); + $notFoundSuffix = '\' not found'; + $notFoundSuffixLen = strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return; + } + + if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { + return; + } + + foreach (array('class', 'interface', 'trait') as $typeName) { + $prefix = ucfirst($typeName).' \''; + $prefixLen = strlen($prefix); + if (0 !== strpos($error['message'], $prefix)) { + continue; + } + + $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { + $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); + $tail = ' for another namespace?'; + } else { + $className = $fullyQualifiedClassName; + $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); + $tail = '?'; + } + + if ($candidates = $this->getClassCandidates($className)) { + $tail = array_pop($candidates).'"?'; + if ($candidates) { + $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; + } else { + $tail = ' for "'.$tail; + } + } + $message .= "\nDid you forget a \"use\" statement".$tail; + + return new ClassNotFoundException($message, $exception); + } + } + + /** + * Tries to guess the full namespace for a given class name. + * + * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer + * autoloader (that should cover all common cases). + * + * @param string $class A class name (without its namespace) + * + * @return array An array of possible fully qualified class names + */ + private function getClassCandidates($class) + { + if (!is_array($functions = spl_autoload_functions())) { + return array(); + } + + // find Symfony and Composer autoloaders + $classes = array(); + + foreach ($functions as $function) { + if (!is_array($function)) { + continue; + } + // get class loaders wrapped by DebugClassLoader + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + + if (!is_array($function)) { + continue; + } + } + + if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) { + foreach ($function[0]->getPrefixes() as $prefix => $paths) { + foreach ($paths as $path) { + $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); + } + } + } + if ($function[0] instanceof ComposerClassLoader) { + foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { + foreach ($paths as $path) { + $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); + } + } + } + } + + return array_unique($classes); + } + + /** + * @param string $path + * @param string $class + * @param string $prefix + * + * @return array + */ + private function findClassInPath($path, $class, $prefix) + { + if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { + return array(); + } + + $classes = array(); + $filename = $class.'.php'; + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { + $classes[] = $class; + } + } + + return $classes; + } + + /** + * @param string $path + * @param string $file + * @param string $prefix + * + * @return string|null + */ + private function convertFileToClass($path, $file, $prefix) + { + $candidates = array( + // namespaced class + $namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file), + // namespaced class (with target dir) + $prefix.$namespacedClass, + // namespaced class (with target dir and separator) + $prefix.'\\'.$namespacedClass, + // PEAR class + str_replace('\\', '_', $namespacedClass), + // PEAR class (with target dir) + str_replace('\\', '_', $prefix.$namespacedClass), + // PEAR class (with target dir and separator) + str_replace('\\', '_', $prefix.'\\'.$namespacedClass), + ); + + if ($prefix) { + $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); + } + + // We cannot use the autoloader here as most of them use require; but if the class + // is not found, the new autoloader call will require the file again leading to a + // "cannot redeclare class" error. + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + + require_once $file; + + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + } + + /** + * @param string $class + * + * @return bool + */ + private function classExists($class) + { + return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); + } +} diff --git a/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php b/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php new file mode 100644 index 00000000..6b87eb30 --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.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\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\FatalErrorException; + +/** + * Attempts to convert fatal errors to exceptions. + * + * @author Fabien Potencier + */ +interface FatalErrorHandlerInterface +{ + /** + * Attempts to convert an error into an exception. + * + * @param array $error An array as returned by error_get_last() + * @param FatalErrorException $exception A FatalErrorException instance + * + * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise + */ + public function handleError(array $error, FatalErrorException $exception); +} diff --git a/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php b/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php new file mode 100644 index 00000000..c6f391a7 --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.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\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\UndefinedFunctionException; +use Symfony\Component\Debug\Exception\FatalErrorException; + +/** + * ErrorHandler for undefined functions. + * + * @author Fabien Potencier + */ +class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + $messageLen = strlen($error['message']); + $notFoundSuffix = '()'; + $notFoundSuffixLen = strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return; + } + + if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { + return; + } + + $prefix = 'Call to undefined function '; + $prefixLen = strlen($prefix); + if (0 !== strpos($error['message'], $prefix)) { + return; + } + + $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { + $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); + } else { + $functionName = $fullyQualifiedFunctionName; + $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); + } + + $candidates = array(); + foreach (get_defined_functions() as $type => $definedFunctionNames) { + foreach ($definedFunctionNames as $definedFunctionName) { + if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { + $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); + } else { + $definedFunctionNameBasename = $definedFunctionName; + } + + if ($definedFunctionNameBasename === $functionName) { + $candidates[] = '\\'.$definedFunctionName; + } + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedFunctionException($message, $exception); + } +} diff --git a/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php b/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php new file mode 100644 index 00000000..6fa62b6f --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\UndefinedMethodException; + +/** + * ErrorHandler for undefined methods. + * + * @author Grégoire Pineau + */ +class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches); + if (!$matches) { + return; + } + + $className = $matches[1]; + $methodName = $matches[2]; + + $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className); + + if (!class_exists($className) || null === $methods = get_class_methods($className)) { + // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class) + return new UndefinedMethodException($message, $exception); + } + + $candidates = array(); + foreach ($methods as $definedMethodName) { + $lev = levenshtein($methodName, $definedMethodName); + if ($lev <= strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) { + $candidates[] = $definedMethodName; + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedMethodException($message, $exception); + } +} diff --git a/vendor/symfony/debug/LICENSE b/vendor/symfony/debug/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/debug/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/debug/README.md b/vendor/symfony/debug/README.md new file mode 100644 index 00000000..a1d16175 --- /dev/null +++ b/vendor/symfony/debug/README.md @@ -0,0 +1,13 @@ +Debug Component +=============== + +The Debug component provides tools to ease debugging PHP code. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/debug/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/debug/Resources/ext/README.md b/vendor/symfony/debug/Resources/ext/README.md new file mode 100644 index 00000000..25dccf07 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/README.md @@ -0,0 +1,134 @@ +Symfony Debug Extension for PHP 5 +================================= + +This extension publishes several functions to help building powerful debugging tools. +It is compatible with PHP 5.3, 5.4, 5.5 and 5.6; with ZTS and non-ZTS modes. +It is not required thus not provided for PHP 7. + +symfony_zval_info() +------------------- + +- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP, +- does work with references, preventing memory copying. + +Its behavior is about the same as: + +```php + gettype($array[$key]), + 'zval_hash' => /* hashed memory address of $array[$key] */, + 'zval_refcount' => /* internal zval refcount of $array[$key] */, + 'zval_isref' => /* is_ref status of $array[$key] */, + ); + + switch ($info['type']) { + case 'object': + $info += array( + 'object_class' => get_class($array[$key]), + 'object_refcount' => /* internal object refcount of $array[$key] */, + 'object_hash' => spl_object_hash($array[$key]), + 'object_handle' => /* internal object handle $array[$key] */, + ); + break; + + case 'resource': + $info += array( + 'resource_handle' => (int) $array[$key], + 'resource_type' => get_resource_type($array[$key]), + 'resource_refcount' => /* internal resource refcount of $array[$key] */, + ); + break; + + case 'array': + $info += array( + 'array_count' => count($array[$key]), + ); + break; + + case 'string': + $info += array( + 'strlen' => strlen($array[$key]), + ); + break; + } + + return $info; +} +``` + +symfony_debug_backtrace() +------------------------- + +This function works like debug_backtrace(), except that it can fetch the full backtrace in case of fatal errors: + +```php +function foo() { fatal(); } +function bar() { foo(); } + +function sd() { var_dump(symfony_debug_backtrace()); } + +register_shutdown_function('sd'); + +bar(); + +/* Will output +Fatal error: Call to undefined function fatal() in foo.php on line 42 +array(3) { + [0]=> + array(2) { + ["function"]=> + string(2) "sd" + ["args"]=> + array(0) { + } + } + [1]=> + array(4) { + ["file"]=> + string(7) "foo.php" + ["line"]=> + int(1) + ["function"]=> + string(3) "foo" + ["args"]=> + array(0) { + } + } + [2]=> + array(4) { + ["file"]=> + string(102) "foo.php" + ["line"]=> + int(2) + ["function"]=> + string(3) "bar" + ["args"]=> + array(0) { + } + } +} +*/ +``` + +Usage +----- + +To enable the extension from source, run: + +``` + phpize + ./configure + make + sudo make install +``` diff --git a/vendor/symfony/debug/Resources/ext/config.m4 b/vendor/symfony/debug/Resources/ext/config.m4 new file mode 100644 index 00000000..3c560471 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/config.m4 @@ -0,0 +1,63 @@ +dnl $Id$ +dnl config.m4 for extension symfony_debug + +dnl Comments in this file start with the string 'dnl'. +dnl Remove where necessary. This file will not work +dnl without editing. + +dnl If your extension references something external, use with: + +dnl PHP_ARG_WITH(symfony_debug, for symfony_debug support, +dnl Make sure that the comment is aligned: +dnl [ --with-symfony_debug Include symfony_debug support]) + +dnl Otherwise use enable: + +PHP_ARG_ENABLE(symfony_debug, whether to enable symfony_debug support, +dnl Make sure that the comment is aligned: +[ --enable-symfony_debug Enable symfony_debug support]) + +if test "$PHP_SYMFONY_DEBUG" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-symfony_debug -> check with-path + dnl SEARCH_PATH="/usr/local /usr" # you might want to change this + dnl SEARCH_FOR="/include/symfony_debug.h" # you most likely want to change this + dnl if test -r $PHP_SYMFONY_DEBUG/$SEARCH_FOR; then # path given as parameter + dnl SYMFONY_DEBUG_DIR=$PHP_SYMFONY_DEBUG + dnl else # search default path list + dnl AC_MSG_CHECKING([for symfony_debug files in default path]) + dnl for i in $SEARCH_PATH ; do + dnl if test -r $i/$SEARCH_FOR; then + dnl SYMFONY_DEBUG_DIR=$i + dnl AC_MSG_RESULT(found in $i) + dnl fi + dnl done + dnl fi + dnl + dnl if test -z "$SYMFONY_DEBUG_DIR"; then + dnl AC_MSG_RESULT([not found]) + dnl AC_MSG_ERROR([Please reinstall the symfony_debug distribution]) + dnl fi + + dnl # --with-symfony_debug -> add include path + dnl PHP_ADD_INCLUDE($SYMFONY_DEBUG_DIR/include) + + dnl # --with-symfony_debug -> check for lib and symbol presence + dnl LIBNAME=symfony_debug # you may want to change this + dnl LIBSYMBOL=symfony_debug # you most likely want to change this + + dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + dnl [ + dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SYMFONY_DEBUG_DIR/lib, SYMFONY_DEBUG_SHARED_LIBADD) + dnl AC_DEFINE(HAVE_SYMFONY_DEBUGLIB,1,[ ]) + dnl ],[ + dnl AC_MSG_ERROR([wrong symfony_debug lib version or lib not found]) + dnl ],[ + dnl -L$SYMFONY_DEBUG_DIR/lib -lm + dnl ]) + dnl + dnl PHP_SUBST(SYMFONY_DEBUG_SHARED_LIBADD) + + PHP_NEW_EXTENSION(symfony_debug, symfony_debug.c, $ext_shared) +fi diff --git a/vendor/symfony/debug/Resources/ext/config.w32 b/vendor/symfony/debug/Resources/ext/config.w32 new file mode 100644 index 00000000..487e6913 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +// If your extension references something external, use ARG_WITH +// ARG_WITH("symfony_debug", "for symfony_debug support", "no"); + +// Otherwise, use ARG_ENABLE +// ARG_ENABLE("symfony_debug", "enable symfony_debug support", "no"); + +if (PHP_SYMFONY_DEBUG != "no") { + EXTENSION("symfony_debug", "symfony_debug.c"); +} + diff --git a/vendor/symfony/debug/Resources/ext/php_symfony_debug.h b/vendor/symfony/debug/Resources/ext/php_symfony_debug.h new file mode 100644 index 00000000..26d0e8c0 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/php_symfony_debug.h @@ -0,0 +1,60 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifndef PHP_SYMFONY_DEBUG_H +#define PHP_SYMFONY_DEBUG_H + +extern zend_module_entry symfony_debug_module_entry; +#define phpext_symfony_debug_ptr &symfony_debug_module_entry + +#define PHP_SYMFONY_DEBUG_VERSION "2.7" + +#ifdef PHP_WIN32 +# define PHP_SYMFONY_DEBUG_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_SYMFONY_DEBUG_API __attribute__ ((visibility("default"))) +#else +# define PHP_SYMFONY_DEBUG_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +ZEND_BEGIN_MODULE_GLOBALS(symfony_debug) + intptr_t req_rand_init; + void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); + zval *debug_bt; +ZEND_END_MODULE_GLOBALS(symfony_debug) + +PHP_MINIT_FUNCTION(symfony_debug); +PHP_MSHUTDOWN_FUNCTION(symfony_debug); +PHP_RINIT_FUNCTION(symfony_debug); +PHP_RSHUTDOWN_FUNCTION(symfony_debug); +PHP_MINFO_FUNCTION(symfony_debug); +PHP_GINIT_FUNCTION(symfony_debug); +PHP_GSHUTDOWN_FUNCTION(symfony_debug); + +PHP_FUNCTION(symfony_zval_info); +PHP_FUNCTION(symfony_debug_backtrace); + +static char *_symfony_debug_memory_address_hash(void * TSRMLS_DC); +static const char *_symfony_debug_zval_type(zval *); +static const char* _symfony_debug_get_resource_type(long TSRMLS_DC); +static int _symfony_debug_get_resource_refcount(long TSRMLS_DC); + +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); + +#ifdef ZTS +#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v) +#else +#define SYMFONY_DEBUG_G(v) (symfony_debug_globals.v) +#endif + +#endif /* PHP_SYMFONY_DEBUG_H */ diff --git a/vendor/symfony/debug/Resources/ext/symfony_debug.c b/vendor/symfony/debug/Resources/ext/symfony_debug.c new file mode 100644 index 00000000..0d7cb602 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/symfony_debug.c @@ -0,0 +1,283 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#ifdef ZTS +#include "TSRM.h" +#endif +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_symfony_debug.h" +#include "ext/standard/php_rand.h" +#include "ext/standard/php_lcg.h" +#include "ext/spl/php_spl.h" +#include "Zend/zend_gc.h" +#include "Zend/zend_builtin_functions.h" +#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ +#include "ext/standard/php_array.h" +#include "Zend/zend_interfaces.h" +#include "SAPI.h" + +#define IS_PHP_53 ZEND_EXTENSION_API_NO == 220090626 + +ZEND_DECLARE_MODULE_GLOBALS(symfony_debug) + +ZEND_BEGIN_ARG_INFO_EX(symfony_zval_arginfo, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_ARRAY_INFO(0, array, 0) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +const zend_function_entry symfony_debug_functions[] = { + PHP_FE(symfony_zval_info, symfony_zval_arginfo) + PHP_FE(symfony_debug_backtrace, NULL) + PHP_FE_END +}; + +PHP_FUNCTION(symfony_debug_backtrace) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } +#if IS_PHP_53 + zend_fetch_debug_backtrace(return_value, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(return_value, 1, 0, 0 TSRMLS_CC); +#endif + + if (!SYMFONY_DEBUG_G(debug_bt)) { + return; + } + + php_array_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(SYMFONY_DEBUG_G(debug_bt)), 0 TSRMLS_CC); +} + +PHP_FUNCTION(symfony_zval_info) +{ + zval *key = NULL, *arg = NULL; + zval **data = NULL; + HashTable *array = NULL; + long options = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zh|l", &key, &array, &options) == FAILURE) { + return; + } + + switch (Z_TYPE_P(key)) { + case IS_STRING: + if (zend_symtable_find(array, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&data) == FAILURE) { + return; + } + break; + case IS_LONG: + if (zend_hash_index_find(array, Z_LVAL_P(key), (void **)&data)) { + return; + } + break; + } + + arg = *data; + + array_init(return_value); + + add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1); + add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg TSRMLS_CC), 16, 0); + add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg)); + add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg)); + + if (Z_TYPE_P(arg) == IS_OBJECT) { + char hash[33] = {0}; + + php_spl_object_hash(arg, (char *)hash TSRMLS_CC); + add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1); + add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount); + add_assoc_string(return_value, "object_hash", hash, 1); + add_assoc_long(return_value, "object_handle", Z_OBJ_HANDLE_P(arg)); + } else if (Z_TYPE_P(arg) == IS_ARRAY) { + add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg))); + } else if(Z_TYPE_P(arg) == IS_RESOURCE) { + add_assoc_long(return_value, "resource_handle", Z_LVAL_P(arg)); + add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg) TSRMLS_CC), 1); + add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg) TSRMLS_CC)); + } else if (Z_TYPE_P(arg) == IS_STRING) { + add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg)); + } +} + +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) +{ + TSRMLS_FETCH(); + zval *retval; + + switch (type) { + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_CORE_WARNING: + case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + ALLOC_INIT_ZVAL(retval); +#if IS_PHP_53 + zend_fetch_debug_backtrace(retval, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(retval, 1, 0, 0 TSRMLS_CC); +#endif + SYMFONY_DEBUG_G(debug_bt) = retval; + } + + SYMFONY_DEBUG_G(old_error_cb)(type, error_filename, error_lineno, format, args); +} + +static const char* _symfony_debug_get_resource_type(long rsid TSRMLS_DC) +{ + const char *res_type; + res_type = zend_rsrc_list_get_rsrc_type(rsid TSRMLS_CC); + + if (!res_type) { + return "Unknown"; + } + + return res_type; +} + +static int _symfony_debug_get_resource_refcount(long rsid TSRMLS_DC) +{ + zend_rsrc_list_entry *le; + + if (zend_hash_index_find(&EG(regular_list), rsid, (void **) &le)==SUCCESS) { + return le->refcount; + } + + return 0; +} + +static char *_symfony_debug_memory_address_hash(void *address TSRMLS_DC) +{ + char *result = NULL; + intptr_t address_rand; + + if (!SYMFONY_DEBUG_G(req_rand_init)) { + if (!BG(mt_rand_is_seeded)) { + php_mt_srand(GENERATE_SEED() TSRMLS_CC); + } + SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(TSRMLS_C); + } + + address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init); + + spprintf(&result, 17, "%016zx", address_rand); + + return result; +} + +static const char *_symfony_debug_zval_type(zval *zv) +{ + switch (Z_TYPE_P(zv)) { + case IS_NULL: + return "NULL"; + break; + + case IS_BOOL: + return "boolean"; + break; + + case IS_LONG: + return "integer"; + break; + + case IS_DOUBLE: + return "double"; + break; + + case IS_STRING: + return "string"; + break; + + case IS_ARRAY: + return "array"; + break; + + case IS_OBJECT: + return "object"; + + case IS_RESOURCE: + return "resource"; + + default: + return "unknown type"; + } +} + +zend_module_entry symfony_debug_module_entry = { + STANDARD_MODULE_HEADER, + "symfony_debug", + symfony_debug_functions, + PHP_MINIT(symfony_debug), + PHP_MSHUTDOWN(symfony_debug), + PHP_RINIT(symfony_debug), + PHP_RSHUTDOWN(symfony_debug), + PHP_MINFO(symfony_debug), + PHP_SYMFONY_DEBUG_VERSION, + PHP_MODULE_GLOBALS(symfony_debug), + PHP_GINIT(symfony_debug), + PHP_GSHUTDOWN(symfony_debug), + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +#ifdef COMPILE_DL_SYMFONY_DEBUG +ZEND_GET_MODULE(symfony_debug) +#endif + +PHP_GINIT_FUNCTION(symfony_debug) +{ + memset(symfony_debug_globals, 0 , sizeof(*symfony_debug_globals)); +} + +PHP_GSHUTDOWN_FUNCTION(symfony_debug) +{ + +} + +PHP_MINIT_FUNCTION(symfony_debug) +{ + SYMFONY_DEBUG_G(old_error_cb) = zend_error_cb; + zend_error_cb = symfony_debug_error_cb; + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(symfony_debug) +{ + zend_error_cb = SYMFONY_DEBUG_G(old_error_cb); + + return SUCCESS; +} + +PHP_RINIT_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_RSHUTDOWN_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_MINFO_FUNCTION(symfony_debug) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "Symfony Debug support", "enabled"); + php_info_print_table_header(2, "Symfony Debug version", PHP_SYMFONY_DEBUG_VERSION); + php_info_print_table_end(); +} diff --git a/vendor/symfony/debug/Resources/ext/tests/001.phpt b/vendor/symfony/debug/Resources/ext/tests/001.phpt new file mode 100644 index 00000000..15e183a7 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/001.phpt @@ -0,0 +1,153 @@ +--TEST-- +Test symfony_zval_info API +--SKIPIF-- + +--FILE-- + $int, + 'float' => $float, + 'str' => $str, + 'object' => $object, + 'array' => $array, + 'resource' => $resource, + 'null' => $null, + 'bool' => $bool, + 'refcount' => &$refcount2, +); + +var_dump(symfony_zval_info('int', $var)); +var_dump(symfony_zval_info('float', $var)); +var_dump(symfony_zval_info('str', $var)); +var_dump(symfony_zval_info('object', $var)); +var_dump(symfony_zval_info('array', $var)); +var_dump(symfony_zval_info('resource', $var)); +var_dump(symfony_zval_info('null', $var)); +var_dump(symfony_zval_info('bool', $var)); + +var_dump(symfony_zval_info('refcount', $var)); +var_dump(symfony_zval_info('not-exist', $var)); +?> +--EXPECTF-- +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(6) "double" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(5) { + ["type"]=> + string(6) "string" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["strlen"]=> + int(6) +} +array(8) { + ["type"]=> + string(6) "object" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["object_class"]=> + string(8) "stdClass" + ["object_refcount"]=> + int(1) + ["object_hash"]=> + string(32) "%s" + ["object_handle"]=> + int(%d) +} +array(5) { + ["type"]=> + string(5) "array" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["array_count"]=> + int(2) +} +array(7) { + ["type"]=> + string(8) "resource" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["resource_handle"]=> + int(%d) + ["resource_type"]=> + string(6) "stream" + ["resource_refcount"]=> + int(1) +} +array(4) { + ["type"]=> + string(4) "NULL" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "boolean" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(3) + ["zval_isref"]=> + bool(true) +} +NULL diff --git a/vendor/symfony/debug/Resources/ext/tests/002.phpt b/vendor/symfony/debug/Resources/ext/tests/002.phpt new file mode 100644 index 00000000..2bc6d712 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/002.phpt @@ -0,0 +1,63 @@ +--TEST-- +Test symfony_debug_backtrace in case of fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Call to undefined function notexist() in %s on line %d +Array +( + [0] => Array + ( + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => foo + [args] => Array + ( + ) + + ) + + [2] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +) diff --git a/vendor/symfony/debug/Resources/ext/tests/002_1.phpt b/vendor/symfony/debug/Resources/ext/tests/002_1.phpt new file mode 100644 index 00000000..4e9e34f1 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/002_1.phpt @@ -0,0 +1,46 @@ +--TEST-- +Test symfony_debug_backtrace in case of non fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Array +( + [0] => Array + ( + [file] => %s + [line] => %d + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +) diff --git a/vendor/symfony/debug/Resources/ext/tests/003.phpt b/vendor/symfony/debug/Resources/ext/tests/003.phpt new file mode 100644 index 00000000..2a494e27 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/003.phpt @@ -0,0 +1,85 @@ +--TEST-- +Test ErrorHandler in case of fatal error +--SKIPIF-- + +--FILE-- +setExceptionHandler('print_r'); + +if (function_exists('xdebug_disable')) { + xdebug_disable(); +} + +bar(); +?> +--EXPECTF-- +Fatal error: Call to undefined function Symfony\Component\Debug\notexist() in %s on line %d +Symfony\Component\Debug\Exception\UndefinedFunctionException Object +( + [message:protected] => Attempted to call function "notexist" from namespace "Symfony\Component\Debug". + [string:Exception:private] => + [code:protected] => 0 + [file:protected] => %s + [line:protected] => %d + [trace:Exception:private] => Array + ( + [0] => Array + ( +%A [function] => Symfony\Component\Debug\foo +%A [args] => Array + ( + ) + + ) + + [1] => Array + ( +%A [function] => Symfony\Component\Debug\bar +%A [args] => Array + ( + ) + + ) +%A + ) + + [previous:Exception:private] => + [severity:protected] => 1 +) diff --git a/vendor/symfony/debug/Tests/DebugClassLoaderTest.php b/vendor/symfony/debug/Tests/DebugClassLoaderTest.php new file mode 100644 index 00000000..f1e3fb7c --- /dev/null +++ b/vendor/symfony/debug/Tests/DebugClassLoaderTest.php @@ -0,0 +1,368 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\Debug\ErrorHandler; + +class DebugClassLoaderTest extends TestCase +{ + /** + * @var int Error reporting level before running tests + */ + private $errorReporting; + + private $loader; + + protected function setUp() + { + $this->errorReporting = error_reporting(E_ALL); + $this->loader = new ClassLoader(); + spl_autoload_register(array($this->loader, 'loadClass'), true, true); + DebugClassLoader::enable(); + } + + protected function tearDown() + { + DebugClassLoader::disable(); + spl_autoload_unregister(array($this->loader, 'loadClass')); + error_reporting($this->errorReporting); + } + + public function testIdempotence() + { + DebugClassLoader::enable(); + + $functions = spl_autoload_functions(); + foreach ($functions as $function) { + if (is_array($function) && $function[0] instanceof DebugClassLoader) { + $reflClass = new \ReflectionClass($function[0]); + $reflProp = $reflClass->getProperty('classLoader'); + $reflProp->setAccessible(true); + + $this->assertNotInstanceOf('Symfony\Component\Debug\DebugClassLoader', $reflProp->getValue($function[0])); + + return; + } + } + + $this->fail('DebugClassLoader did not register'); + } + + public function testUnsilencing() + { + if (\PHP_VERSION_ID >= 70000) { + $this->markTestSkipped('PHP7 throws exceptions, unsilencing is not required anymore.'); + } + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM is not handled in this test case.'); + } + + ob_start(); + + $this->iniSet('log_errors', 0); + $this->iniSet('display_errors', 1); + + // See below: this will fail with parse error + // but this should not be @-silenced. + @class_exists(__NAMESPACE__.'\TestingUnsilencing', true); + + $output = ob_get_clean(); + + $this->assertStringMatchesFormat('%aParse error%a', $output); + } + + public function testStacking() + { + // the ContextErrorException must not be loaded to test the workaround + // for https://bugs.php.net/65322. + if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) { + $this->markTestSkipped('The ContextErrorException class is already loaded.'); + } + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM is not handled in this test case.'); + } + + ErrorHandler::register(); + + try { + // Trigger autoloading + E_STRICT at compile time + // which in turn triggers $errorHandler->handle() + // that again triggers autoloading for ContextErrorException. + // Error stacking works around the bug above and everything is fine. + + eval(' + namespace '.__NAMESPACE__.'; + class ChildTestingStacking extends TestingStacking { function foo($bar) {} } + '); + $this->fail('ContextErrorException expected'); + } catch (\ErrorException $exception) { + // if an exception is thrown, the test passed + $this->assertStringStartsWith(__FILE__, $exception->getFile()); + if (\PHP_VERSION_ID < 70000) { + $this->assertRegExp('/^Runtime Notice: Declaration/', $exception->getMessage()); + $this->assertEquals(E_STRICT, $exception->getSeverity()); + } else { + $this->assertRegExp('/^Warning: Declaration/', $exception->getMessage()); + $this->assertEquals(E_WARNING, $exception->getSeverity()); + } + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + /** + * @expectedException \RuntimeException + */ + public function testNameCaseMismatch() + { + class_exists(__NAMESPACE__.'\TestingCaseMismatch', true); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Case mismatch between class and real file names + */ + public function testFileCaseMismatch() + { + if (!file_exists(__DIR__.'/Fixtures/CaseMismatch.php')) { + $this->markTestSkipped('Can only be run on case insensitive filesystems'); + } + + class_exists(__NAMESPACE__.'\Fixtures\CaseMismatch', true); + } + + /** + * @expectedException \RuntimeException + */ + public function testPsr4CaseMismatch() + { + class_exists(__NAMESPACE__.'\Fixtures\Psr4CaseMismatch', true); + } + + public function testNotPsr0() + { + $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0', true)); + } + + public function testNotPsr0Bis() + { + $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\NotPSR0bis', true)); + } + + public function testClassAlias() + { + $this->assertTrue(class_exists(__NAMESPACE__.'\Fixtures\ClassAlias', true)); + } + + /** + * @dataProvider provideDeprecatedSuper + */ + public function testDeprecatedSuper($class, $super, $type) + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_DEPRECATED); + + class_exists('Test\\'.__NAMESPACE__.'\\'.$class, true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The "Test\Symfony\Component\Debug\Tests\\'.$class.'" class '.$type.' "Symfony\Component\Debug\Tests\Fixtures\\'.$super.'" that is deprecated but this is a test deprecation notice.', + ); + + $this->assertSame($xError, $lastError); + } + + public function provideDeprecatedSuper() + { + return array( + array('DeprecatedInterfaceClass', 'DeprecatedInterface', 'implements'), + array('DeprecatedParentClass', 'DeprecatedClass', 'extends'), + ); + } + + public function testInterfaceExtendsDeprecatedInterface() + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Test\\'.__NAMESPACE__.'\\NonDeprecatedInterfaceClass', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_NOTICE, + 'message' => '', + ); + + $this->assertSame($xError, $lastError); + } + + public function testDeprecatedSuperInSameNamespace() + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_NOTICE, + 'message' => '', + ); + + $this->assertSame($xError, $lastError); + } + + public function testReservedForPhp7() + { + if (\PHP_VERSION_ID >= 70000) { + $this->markTestSkipped('PHP7 already prevents using reserved names.'); + } + + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Test\\'.__NAMESPACE__.'\\Float', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The "Test\Symfony\Component\Debug\Tests\Float" class uses the reserved name "Float", it will break on PHP 7 and higher', + ); + + $this->assertSame($xError, $lastError); + } + + public function testExtendedFinalClass() + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists('Test\\'.__NAMESPACE__.'\\ExtendsFinalClass', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass" class is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass".', + ); + + $this->assertSame($xError, $lastError); + } + + public function testExtendedFinalMethod() + { + set_error_handler(function () { return false; }); + $e = error_reporting(0); + trigger_error('', E_USER_NOTICE); + + class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true); + + error_reporting($e); + restore_error_handler(); + + $lastError = error_get_last(); + unset($lastError['file'], $lastError['line']); + + $xError = array( + 'type' => E_USER_DEPRECATED, + 'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".', + ); + + $this->assertSame($xError, $lastError); + } +} + +class ClassLoader +{ + public function loadClass($class) + { + } + + public function getClassMap() + { + return array(__NAMESPACE__.'\Fixtures\NotPSR0bis' => __DIR__.'/Fixtures/notPsr0Bis.php'); + } + + public function findFile($class) + { + $fixtureDir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR; + + if (__NAMESPACE__.'\TestingUnsilencing' === $class) { + eval('-- parse error --'); + } elseif (__NAMESPACE__.'\TestingStacking' === $class) { + eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }'); + } elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) { + eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}'); + } elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) { + return $fixtureDir.'CaseMismatch.php'; + } elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) { + return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php'; + } elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) { + return $fixtureDir.'reallyNotPsr0.php'; + } elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) { + return $fixtureDir.'notPsr0Bis.php'; + } elseif (__NAMESPACE__.'\Fixtures\DeprecatedInterface' === $class) { + return $fixtureDir.'DeprecatedInterface.php'; + } elseif (__NAMESPACE__.'\Fixtures\FinalClass' === $class) { + return $fixtureDir.'FinalClass.php'; + } elseif (__NAMESPACE__.'\Fixtures\FinalMethod' === $class) { + return $fixtureDir.'FinalMethod.php'; + } elseif (__NAMESPACE__.'\Fixtures\ExtendedFinalMethod' === $class) { + return $fixtureDir.'ExtendedFinalMethod.php'; + } elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) { + eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); + } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedParentClass extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); + } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedInterfaceClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class DeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\DeprecatedInterface {}'); + } elseif ('Test\\'.__NAMESPACE__.'\NonDeprecatedInterfaceClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}'); + } elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class Float {}'); + } elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}'); + } + } +} diff --git a/vendor/symfony/debug/Tests/ErrorHandlerTest.php b/vendor/symfony/debug/Tests/ErrorHandlerTest.php new file mode 100644 index 00000000..2fccf2db --- /dev/null +++ b/vendor/symfony/debug/Tests/ErrorHandlerTest.php @@ -0,0 +1,529 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LogLevel; +use Symfony\Component\Debug\BufferingLogger; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\Exception\SilencedErrorContext; + +/** + * ErrorHandlerTest. + * + * @author Robert Schönthal + * @author Nicolas Grekas + */ +class ErrorHandlerTest extends TestCase +{ + public function testRegister() + { + $handler = ErrorHandler::register(); + + try { + $this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler); + $this->assertSame($handler, ErrorHandler::register()); + + $newHandler = new ErrorHandler(); + + $this->assertSame($newHandler, ErrorHandler::register($newHandler, false)); + $h = set_error_handler('var_dump'); + restore_error_handler(); + $this->assertSame(array($handler, 'handleError'), $h); + + try { + $this->assertSame($newHandler, ErrorHandler::register($newHandler, true)); + $h = set_error_handler('var_dump'); + restore_error_handler(); + $this->assertSame(array($newHandler, 'handleError'), $h); + } catch (\Exception $e) { + } + + restore_error_handler(); + restore_exception_handler(); + + if (isset($e)) { + throw $e; + } + } catch (\Exception $e) { + } + + restore_error_handler(); + restore_exception_handler(); + + if (isset($e)) { + throw $e; + } + } + + public function testNotice() + { + ErrorHandler::register(); + + try { + self::triggerNotice($this); + $this->fail('ErrorException expected'); + } catch (\ErrorException $exception) { + // if an exception is thrown, the test passed + $this->assertEquals(E_NOTICE, $exception->getSeverity()); + $this->assertEquals(__FILE__, $exception->getFile()); + $this->assertRegExp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage()); + + $trace = $exception->getTrace(); + + $this->assertEquals(__FILE__, $trace[0]['file']); + $this->assertEquals(__CLASS__, $trace[0]['class']); + $this->assertEquals('triggerNotice', $trace[0]['function']); + $this->assertEquals('::', $trace[0]['type']); + + $this->assertEquals(__FILE__, $trace[0]['file']); + $this->assertEquals(__CLASS__, $trace[1]['class']); + $this->assertEquals(__FUNCTION__, $trace[1]['function']); + $this->assertEquals('->', $trace[1]['type']); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + // dummy function to test trace in error handler. + private static function triggerNotice($that) + { + // dummy variable to check for in error handler. + $foobar = 123; + $that->assertSame('', $foo.$foo.$bar); + } + + public function testConstruct() + { + try { + $handler = ErrorHandler::register(); + $handler->throwAt(3, true); + $this->assertEquals(3 | E_RECOVERABLE_ERROR | E_USER_ERROR, $handler->throwAt(0)); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testDefaultLogger() + { + try { + $handler = ErrorHandler::register(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $handler->setDefaultLogger($logger, E_NOTICE); + $handler->setDefaultLogger($logger, array(E_USER_NOTICE => LogLevel::CRITICAL)); + + $loggers = array( + E_DEPRECATED => array(null, LogLevel::INFO), + E_USER_DEPRECATED => array(null, LogLevel::INFO), + E_NOTICE => array($logger, LogLevel::WARNING), + E_USER_NOTICE => array($logger, LogLevel::CRITICAL), + E_STRICT => array(null, LogLevel::WARNING), + E_WARNING => array(null, LogLevel::WARNING), + E_USER_WARNING => array(null, LogLevel::WARNING), + E_COMPILE_WARNING => array(null, LogLevel::WARNING), + E_CORE_WARNING => array(null, LogLevel::WARNING), + E_USER_ERROR => array(null, LogLevel::CRITICAL), + E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL), + E_COMPILE_ERROR => array(null, LogLevel::CRITICAL), + E_PARSE => array(null, LogLevel::CRITICAL), + E_ERROR => array(null, LogLevel::CRITICAL), + E_CORE_ERROR => array(null, LogLevel::CRITICAL), + ); + $this->assertSame($loggers, $handler->setLoggers(array())); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleError() + { + try { + $handler = ErrorHandler::register(); + $handler->throwAt(0, true); + $this->assertFalse($handler->handleError(0, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(3, true); + $this->assertFalse($handler->handleError(4, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(3, true); + try { + $handler->handleError(4, 'foo', 'foo.php', 12, array()); + } catch (\ErrorException $e) { + $this->assertSame('Parse Error: foo', $e->getMessage()); + $this->assertSame(4, $e->getSeverity()); + $this->assertSame('foo.php', $e->getFile()); + $this->assertSame(12, $e->getLine()); + } + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(E_USER_DEPRECATED, true); + $this->assertFalse($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $handler = ErrorHandler::register(); + $handler->throwAt(E_DEPRECATED, true); + $this->assertFalse($handler->handleError(E_DEPRECATED, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $warnArgCheck = function ($logLevel, $message, $context) { + $this->assertEquals('info', $logLevel); + $this->assertEquals('User Deprecated: foo', $message); + $this->assertArrayHasKey('exception', $context); + $exception = $context['exception']; + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertSame('User Deprecated: foo', $exception->getMessage()); + $this->assertSame(E_USER_DEPRECATED, $exception->getSeverity()); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($warnArgCheck)) + ; + + $handler = ErrorHandler::register(); + $handler->setDefaultLogger($logger, E_USER_DEPRECATED); + $this->assertTrue($handler->handleError(E_USER_DEPRECATED, 'foo', 'foo.php', 12, array())); + + restore_error_handler(); + restore_exception_handler(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $logArgCheck = function ($level, $message, $context) { + $this->assertEquals('Notice: Undefined variable: undefVar', $message); + $this->assertArrayHasKey('exception', $context); + $exception = $context['exception']; + $this->assertInstanceOf(SilencedErrorContext::class, $exception); + $this->assertSame(E_NOTICE, $exception->getSeverity()); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler = ErrorHandler::register(); + $handler->setDefaultLogger($logger, E_NOTICE); + $handler->screamAt(E_NOTICE); + unset($undefVar); + @$undefVar++; + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + public function testHandleUserError() + { + try { + $handler = ErrorHandler::register(); + $handler->throwAt(0, true); + + $e = null; + $x = new \Exception('Foo'); + + try { + $f = new Fixtures\ToStringThrower($x); + $f .= ''; // Trigger $f->__toString() + } catch (\Exception $e) { + } + + $this->assertSame($x, $e); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testHandleDeprecation() + { + $logArgCheck = function ($level, $message, $context) { + $this->assertEquals(LogLevel::INFO, $level); + $this->assertArrayHasKey('exception', $context); + $exception = $context['exception']; + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertSame('User Deprecated: Foo deprecation', $exception->getMessage()); + }; + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler = new ErrorHandler(); + $handler->setDefaultLogger($logger); + @$handler->handleError(E_USER_DEPRECATED, 'Foo deprecation', __FILE__, __LINE__, array()); + } + + public function testHandleException() + { + try { + $handler = ErrorHandler::register(); + + $exception = new \Exception('foo'); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $logArgCheck = function ($level, $message, $context) { + $this->assertSame('Uncaught Exception: foo', $message); + $this->assertArrayHasKey('exception', $context); + $this->assertInstanceOf(\Exception::class, $context['exception']); + }; + + $logger + ->expects($this->exactly(2)) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler->setDefaultLogger($logger, E_ERROR); + + try { + $handler->handleException($exception); + $this->fail('Exception expected'); + } catch (\Exception $e) { + $this->assertSame($exception, $e); + } + + $handler->setExceptionHandler(function ($e) use ($exception) { + $this->assertSame($exception, $e); + }); + + $handler->handleException($exception); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testErrorStacking() + { + try { + $handler = ErrorHandler::register(); + $handler->screamAt(E_USER_WARNING); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $logger + ->expects($this->exactly(2)) + ->method('log') + ->withConsecutive( + array($this->equalTo(LogLevel::WARNING), $this->equalTo('Dummy log')), + array($this->equalTo(LogLevel::DEBUG), $this->equalTo('User Warning: Silenced warning')) + ) + ; + + $handler->setDefaultLogger($logger, array(E_USER_WARNING => LogLevel::WARNING)); + + ErrorHandler::stackErrors(); + @trigger_error('Silenced warning', E_USER_WARNING); + $logger->log(LogLevel::WARNING, 'Dummy log'); + ErrorHandler::unstackErrors(); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } + + public function testBootstrappingLogger() + { + $bootLogger = new BufferingLogger(); + $handler = new ErrorHandler($bootLogger); + + $loggers = array( + E_DEPRECATED => array($bootLogger, LogLevel::INFO), + E_USER_DEPRECATED => array($bootLogger, LogLevel::INFO), + E_NOTICE => array($bootLogger, LogLevel::WARNING), + E_USER_NOTICE => array($bootLogger, LogLevel::WARNING), + E_STRICT => array($bootLogger, LogLevel::WARNING), + E_WARNING => array($bootLogger, LogLevel::WARNING), + E_USER_WARNING => array($bootLogger, LogLevel::WARNING), + E_COMPILE_WARNING => array($bootLogger, LogLevel::WARNING), + E_CORE_WARNING => array($bootLogger, LogLevel::WARNING), + E_USER_ERROR => array($bootLogger, LogLevel::CRITICAL), + E_RECOVERABLE_ERROR => array($bootLogger, LogLevel::CRITICAL), + E_COMPILE_ERROR => array($bootLogger, LogLevel::CRITICAL), + E_PARSE => array($bootLogger, LogLevel::CRITICAL), + E_ERROR => array($bootLogger, LogLevel::CRITICAL), + E_CORE_ERROR => array($bootLogger, LogLevel::CRITICAL), + ); + + $this->assertSame($loggers, $handler->setLoggers(array())); + + $handler->handleError(E_DEPRECATED, 'Foo message', __FILE__, 123, array()); + + $logs = $bootLogger->cleanLogs(); + + $this->assertCount(1, $logs); + $log = $logs[0]; + $this->assertSame('info', $log[0]); + $this->assertSame('Deprecated: Foo message', $log[1]); + $this->assertArrayHasKey('exception', $log[2]); + $exception = $log[2]['exception']; + $this->assertInstanceOf(\ErrorException::class, $exception); + $this->assertSame('Deprecated: Foo message', $exception->getMessage()); + $this->assertSame(__FILE__, $exception->getFile()); + $this->assertSame(123, $exception->getLine()); + $this->assertSame(E_DEPRECATED, $exception->getSeverity()); + + $bootLogger->log(LogLevel::WARNING, 'Foo message', array('exception' => $exception)); + + $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $mockLogger->expects($this->once()) + ->method('log') + ->with(LogLevel::WARNING, 'Foo message', array('exception' => $exception)); + + $handler->setLoggers(array(E_DEPRECATED => array($mockLogger, LogLevel::WARNING))); + } + + public function testSettingLoggerWhenExceptionIsBuffered() + { + $bootLogger = new BufferingLogger(); + $handler = new ErrorHandler($bootLogger); + + $exception = new \Exception('Foo message'); + + $mockLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $mockLogger->expects($this->once()) + ->method('log') + ->with(LogLevel::CRITICAL, 'Uncaught Exception: Foo message', array('exception' => $exception)); + + $handler->setExceptionHandler(function () use ($handler, $mockLogger) { + $handler->setDefaultLogger($mockLogger); + }); + + $handler->handleException($exception); + } + + public function testHandleFatalError() + { + try { + $handler = ErrorHandler::register(); + + $error = array( + 'type' => E_PARSE, + 'message' => 'foo', + 'file' => 'bar', + 'line' => 123, + ); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $logArgCheck = function ($level, $message, $context) { + $this->assertEquals('Fatal Parse Error: foo', $message); + $this->assertArrayHasKey('exception', $context); + $this->assertInstanceOf(\Exception::class, $context['exception']); + }; + + $logger + ->expects($this->once()) + ->method('log') + ->will($this->returnCallback($logArgCheck)) + ; + + $handler->setDefaultLogger($logger, E_PARSE); + + $handler->handleFatalError($error); + + restore_error_handler(); + restore_exception_handler(); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + + throw $e; + } + } + + /** + * @requires PHP 7 + */ + public function testHandleErrorException() + { + $exception = new \Error("Class 'Foo' not found"); + + $handler = new ErrorHandler(); + $handler->setExceptionHandler(function () use (&$args) { + $args = func_get_args(); + }); + + $handler->handleException($exception); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]); + $this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage()); + } + + public function testHandleFatalErrorOnHHVM() + { + try { + $handler = ErrorHandler::register(); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger + ->expects($this->once()) + ->method('log') + ->with( + $this->equalTo(LogLevel::CRITICAL), + $this->equalTo('Fatal Error: foo') + ) + ; + + $handler->setDefaultLogger($logger, E_ERROR); + + $error = array( + 'type' => E_ERROR + 0x1000000, // This error level is used by HHVM for fatal errors + 'message' => 'foo', + 'file' => 'bar', + 'line' => 123, + 'context' => array(123), + 'backtrace' => array(456), + ); + + call_user_func_array(array($handler, 'handleError'), $error); + $handler->handleFatalError($error); + } finally { + restore_error_handler(); + restore_exception_handler(); + } + } +} diff --git a/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php b/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php new file mode 100644 index 00000000..e7762bde --- /dev/null +++ b/vendor/symfony/debug/Tests/Exception/FlattenExceptionTest.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\GoneHttpException; +use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException; +use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; +use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; +use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; + +class FlattenExceptionTest extends TestCase +{ + public function testStatusCode() + { + $flattened = FlattenException::create(new \RuntimeException(), 403); + $this->assertEquals('403', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new \RuntimeException()); + $this->assertEquals('500', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new NotFoundHttpException()); + $this->assertEquals('404', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"')); + $this->assertEquals('401', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new BadRequestHttpException()); + $this->assertEquals('400', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new NotAcceptableHttpException()); + $this->assertEquals('406', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new ConflictHttpException()); + $this->assertEquals('409', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST'))); + $this->assertEquals('405', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new AccessDeniedHttpException()); + $this->assertEquals('403', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new GoneHttpException()); + $this->assertEquals('410', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new LengthRequiredHttpException()); + $this->assertEquals('411', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new PreconditionFailedHttpException()); + $this->assertEquals('412', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new PreconditionRequiredHttpException()); + $this->assertEquals('428', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException()); + $this->assertEquals('503', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException()); + $this->assertEquals('429', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new UnsupportedMediaTypeHttpException()); + $this->assertEquals('415', $flattened->getStatusCode()); + + if (class_exists(SuspiciousOperationException::class)) { + $flattened = FlattenException::create(new SuspiciousOperationException()); + $this->assertEquals('400', $flattened->getStatusCode()); + } + } + + public function testHeadersForHttpException() + { + $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST'))); + $this->assertEquals(array('Allow' => 'POST'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"')); + $this->assertEquals(array('WWW-Authenticate' => 'Basic realm="My Realm"'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException('Fri, 31 Dec 1999 23:59:59 GMT')); + $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException(120)); + $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException('Fri, 31 Dec 1999 23:59:59 GMT')); + $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException(120)); + $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testFlattenHttpException(\Exception $exception, $statusCode) + { + $flattened = FlattenException::create($exception); + $flattened2 = FlattenException::create($exception); + + $flattened->setPrevious($flattened2); + + $this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.'); + $this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.'); + $this->assertInstanceOf($flattened->getClass(), $exception, 'The class is set to the class of the original exception'); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testPrevious(\Exception $exception, $statusCode) + { + $flattened = FlattenException::create($exception); + $flattened2 = FlattenException::create($exception); + + $flattened->setPrevious($flattened2); + + $this->assertSame($flattened2, $flattened->getPrevious()); + + $this->assertSame(array($flattened2), $flattened->getAllPrevious()); + } + + /** + * @requires PHP 7.0 + */ + public function testPreviousError() + { + $exception = new \Exception('test', 123, new \ParseError('Oh noes!', 42)); + + $flattened = FlattenException::create($exception)->getPrevious(); + + $this->assertEquals($flattened->getMessage(), 'Parse error: Oh noes!', 'The message is copied from the original exception.'); + $this->assertEquals($flattened->getCode(), 42, 'The code is copied from the original exception.'); + $this->assertEquals($flattened->getClass(), 'Symfony\Component\Debug\Exception\FatalThrowableError', 'The class is set to the class of the original exception'); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testLine(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $this->assertSame($exception->getLine(), $flattened->getLine()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testFile(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $this->assertSame($exception->getFile(), $flattened->getFile()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testToArray(\Exception $exception, $statusCode) + { + $flattened = FlattenException::create($exception); + $flattened->setTrace(array(), 'foo.php', 123); + + $this->assertEquals(array( + array( + 'message' => 'test', + 'class' => 'Exception', + 'trace' => array(array( + 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123, + 'args' => array(), + )), + ), + ), $flattened->toArray()); + } + + public function flattenDataProvider() + { + return array( + array(new \Exception('test', 123), 500), + ); + } + + public function testArguments() + { + $dh = opendir(__DIR__); + $fh = tmpfile(); + + $incomplete = unserialize('O:14:"BogusTestClass":0:{}'); + + $exception = $this->createException(array( + (object) array('foo' => 1), + new NotFoundHttpException(), + $incomplete, + $dh, + $fh, + function () {}, + array(1, 2), + array('foo' => 123), + null, + true, + false, + 0, + 0.0, + '0', + '', + INF, + NAN, + )); + + $flattened = FlattenException::create($exception); + $trace = $flattened->getTrace(); + $args = $trace[1]['args']; + $array = $args[0][1]; + + closedir($dh); + fclose($fh); + + $i = 0; + $this->assertSame(array('object', 'stdClass'), $array[$i++]); + $this->assertSame(array('object', 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException'), $array[$i++]); + $this->assertSame(array('incomplete-object', 'BogusTestClass'), $array[$i++]); + $this->assertSame(array('resource', defined('HHVM_VERSION') ? 'Directory' : 'stream'), $array[$i++]); + $this->assertSame(array('resource', 'stream'), $array[$i++]); + + $args = $array[$i++]; + $this->assertSame($args[0], 'object'); + $this->assertTrue('Closure' === $args[1] || is_subclass_of($args[1], '\Closure'), 'Expect object class name to be Closure or a subclass of Closure.'); + + $this->assertSame(array('array', array(array('integer', 1), array('integer', 2))), $array[$i++]); + $this->assertSame(array('array', array('foo' => array('integer', 123))), $array[$i++]); + $this->assertSame(array('null', null), $array[$i++]); + $this->assertSame(array('boolean', true), $array[$i++]); + $this->assertSame(array('boolean', false), $array[$i++]); + $this->assertSame(array('integer', 0), $array[$i++]); + $this->assertSame(array('float', 0.0), $array[$i++]); + $this->assertSame(array('string', '0'), $array[$i++]); + $this->assertSame(array('string', ''), $array[$i++]); + $this->assertSame(array('float', INF), $array[$i++]); + + // assertEquals() does not like NAN values. + $this->assertEquals($array[$i][0], 'float'); + $this->assertTrue(is_nan($array[$i++][1])); + } + + public function testRecursionInArguments() + { + $a = array('foo', array(2, &$a)); + $exception = $this->createException($a); + + $flattened = FlattenException::create($exception); + $trace = $flattened->getTrace(); + $this->assertContains('*DEEP NESTED ARRAY*', serialize($trace)); + } + + public function testTooBigArray() + { + $a = array(); + for ($i = 0; $i < 20; ++$i) { + for ($j = 0; $j < 50; ++$j) { + for ($k = 0; $k < 10; ++$k) { + $a[$i][$j][$k] = 'value'; + } + } + } + $a[20] = 'value'; + $a[21] = 'value1'; + $exception = $this->createException($a); + + $flattened = FlattenException::create($exception); + $trace = $flattened->getTrace(); + + $this->assertSame($trace[1]['args'][0], array('array', array('array', '*SKIPPED over 10000 entries*'))); + + $serializeTrace = serialize($trace); + + $this->assertContains('*SKIPPED over 10000 entries*', $serializeTrace); + $this->assertNotContains('*value1*', $serializeTrace); + } + + private function createException($foo) + { + return new \Exception(); + } +} diff --git a/vendor/symfony/debug/Tests/ExceptionHandlerTest.php b/vendor/symfony/debug/Tests/ExceptionHandlerTest.php new file mode 100644 index 00000000..0285eff1 --- /dev/null +++ b/vendor/symfony/debug/Tests/ExceptionHandlerTest.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; + +require_once __DIR__.'/HeaderMock.php'; + +class ExceptionHandlerTest extends TestCase +{ + protected function setUp() + { + testHeader(); + } + + protected function tearDown() + { + testHeader(); + } + + public function testDebug() + { + $handler = new ExceptionHandler(false); + + ob_start(); + $handler->sendPhpResponse(new \RuntimeException('Foo')); + $response = ob_get_clean(); + + $this->assertContains('Whoops, looks like something went wrong.', $response); + $this->assertNotContains('
', $response); + + $handler = new ExceptionHandler(true); + + ob_start(); + $handler->sendPhpResponse(new \RuntimeException('Foo')); + $response = ob_get_clean(); + + $this->assertContains('Whoops, looks like something went wrong.', $response); + $this->assertContains('
', $response); + } + + public function testStatusCode() + { + $handler = new ExceptionHandler(false, 'iso8859-1'); + + ob_start(); + $handler->sendPhpResponse(new NotFoundHttpException('Foo')); + $response = ob_get_clean(); + + $this->assertContains('Sorry, the page you are looking for could not be found.', $response); + + $expectedHeaders = array( + array('HTTP/1.0 404', true, null), + array('Content-Type: text/html; charset=iso8859-1', true, null), + ); + + $this->assertSame($expectedHeaders, testHeader()); + } + + public function testHeaders() + { + $handler = new ExceptionHandler(false, 'iso8859-1'); + + ob_start(); + $handler->sendPhpResponse(new MethodNotAllowedHttpException(array('POST'))); + $response = ob_get_clean(); + + $expectedHeaders = array( + array('HTTP/1.0 405', true, null), + array('Allow: POST', false, null), + array('Content-Type: text/html; charset=iso8859-1', true, null), + ); + + $this->assertSame($expectedHeaders, testHeader()); + } + + public function testNestedExceptions() + { + $handler = new ExceptionHandler(true); + ob_start(); + $handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar'))); + $response = ob_get_clean(); + + $this->assertStringMatchesFormat('%A

Foo

%A

Bar

%A', $response); + } + + public function testHandle() + { + $exception = new \Exception('foo'); + + $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(array('sendPhpResponse'))->getMock(); + $handler + ->expects($this->exactly(2)) + ->method('sendPhpResponse'); + + $handler->handle($exception); + + $handler->setHandler(function ($e) use ($exception) { + $this->assertSame($exception, $e); + }); + + $handler->handle($exception); + } + + public function testHandleOutOfMemoryException() + { + $exception = new OutOfMemoryException('foo', 0, E_ERROR, __FILE__, __LINE__); + + $handler = $this->getMockBuilder('Symfony\Component\Debug\ExceptionHandler')->setMethods(array('sendPhpResponse'))->getMock(); + $handler + ->expects($this->once()) + ->method('sendPhpResponse'); + + $handler->setHandler(function ($e) { + $this->fail('OutOfMemoryException should bypass the handler'); + }); + + $handler->handle($exception); + } +} diff --git a/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php b/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php new file mode 100644 index 00000000..65c80fc1 --- /dev/null +++ b/vendor/symfony/debug/Tests/FatalErrorHandler/ClassNotFoundFatalErrorHandlerTest.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\FatalErrorHandler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; +use Symfony\Component\Debug\DebugClassLoader; +use Composer\Autoload\ClassLoader as ComposerClassLoader; + +class ClassNotFoundFatalErrorHandlerTest extends TestCase +{ + public static function setUpBeforeClass() + { + foreach (spl_autoload_functions() as $function) { + if (!is_array($function)) { + continue; + } + + // get class loaders wrapped by DebugClassLoader + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + } + + if ($function[0] instanceof ComposerClassLoader) { + $function[0]->add('Symfony_Component_Debug_Tests_Fixtures', dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + break; + } + } + } + + /** + * @dataProvider provideClassNotFoundData + */ + public function testHandleClassNotFound($error, $translatedMessage, $autoloader = null) + { + if ($autoloader) { + // Unregister all autoloaders to ensure the custom provided + // autoloader is the only one to be used during the test run. + $autoloaders = spl_autoload_functions(); + array_map('spl_autoload_unregister', $autoloaders); + spl_autoload_register($autoloader); + } + + $handler = new ClassNotFoundFatalErrorHandler(); + + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + if ($autoloader) { + spl_autoload_unregister($autoloader); + array_map('spl_autoload_register', $autoloaders); + } + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception); + $this->assertSame($translatedMessage, $exception->getMessage()); + $this->assertSame($error['type'], $exception->getSeverity()); + $this->assertSame($error['file'], $exception->getFile()); + $this->assertSame($error['line'], $exception->getLine()); + } + + public function provideClassNotFoundData() + { + $autoloader = new ComposerClassLoader(); + $autoloader->add('Symfony\Component\Debug\Exception\\', realpath(__DIR__.'/../../Exception')); + + $debugClassLoader = new DebugClassLoader(array($autoloader, 'loadClass')); + + return array( + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'WhizBangFactory\' not found', + ), + "Attempted to load class \"WhizBangFactory\" from the global namespace.\nDid you forget a \"use\" statement?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\WhizBangFactory\' not found', + ), + "Attempted to load class \"WhizBangFactory\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'PEARClass\' not found', + ), + "Attempted to load class \"PEARClass\" from the global namespace.\nDid you forget a \"use\" statement for \"Symfony_Component_Debug_Tests_Fixtures_PEARClass\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + array($autoloader, 'loadClass'), + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\Bar\".\nDid you forget a \"use\" statement for \"Symfony\Component\Debug\Exception\UndefinedFunctionException\"?", + array($debugClassLoader, 'loadClass'), + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\UndefinedFunctionException\' not found', + ), + "Attempted to load class \"UndefinedFunctionException\" from namespace \"Foo\\Bar\".\nDid you forget a \"use\" statement for another namespace?", + function ($className) { /* do nothing here */ }, + ), + ); + } + + public function testCannotRedeclareClass() + { + if (!file_exists(__DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP')) { + $this->markTestSkipped('Can only be run on case insensitive filesystems'); + } + + require_once __DIR__.'/../FIXTURES2/REQUIREDTWICE.PHP'; + + $error = array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Class \'Foo\\Bar\\RequiredTwice\' not found', + ); + + $handler = new ClassNotFoundFatalErrorHandler(); + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $exception); + } +} diff --git a/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php b/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.php new file mode 100644 index 00000000..1dc21200 --- /dev/null +++ b/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedFunctionFatalErrorHandlerTest.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\Debug\Tests\FatalErrorHandler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; + +class UndefinedFunctionFatalErrorHandlerTest extends TestCase +{ + /** + * @dataProvider provideUndefinedFunctionData + */ + public function testUndefinedFunction($error, $translatedMessage) + { + $handler = new UndefinedFunctionFatalErrorHandler(); + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedFunctionException', $exception); + // class names are case insensitive and PHP/HHVM do not return the same + $this->assertSame(strtolower($translatedMessage), strtolower($exception->getMessage())); + $this->assertSame($error['type'], $exception->getSeverity()); + $this->assertSame($error['file'], $exception->getFile()); + $this->assertSame($error['line'], $exception->getLine()); + } + + public function provideUndefinedFunctionData() + { + return array( + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function test_namespaced_function()', + ), + "Attempted to call function \"test_namespaced_function\" from the global namespace.\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function Foo\\Bar\\Baz\\test_namespaced_function()', + ), + "Attempted to call function \"test_namespaced_function\" from namespace \"Foo\\Bar\\Baz\".\nDid you mean to call \"\\symfony\\component\\debug\\tests\\fatalerrorhandler\\test_namespaced_function\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function foo()', + ), + 'Attempted to call function "foo" from the global namespace.', + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined function Foo\\Bar\\Baz\\foo()', + ), + 'Attempted to call function "foo" from namespace "Foo\Bar\Baz".', + ), + ); + } +} + +function test_namespaced_function() +{ +} diff --git a/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php b/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php new file mode 100644 index 00000000..739e5b2b --- /dev/null +++ b/vendor/symfony/debug/Tests/FatalErrorHandler/UndefinedMethodFatalErrorHandlerTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\FatalErrorHandler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; + +class UndefinedMethodFatalErrorHandlerTest extends TestCase +{ + /** + * @dataProvider provideUndefinedMethodData + */ + public function testUndefinedMethod($error, $translatedMessage) + { + $handler = new UndefinedMethodFatalErrorHandler(); + $exception = $handler->handleError($error, new FatalErrorException('', 0, $error['type'], $error['file'], $error['line'])); + + $this->assertInstanceOf('Symfony\Component\Debug\Exception\UndefinedMethodException', $exception); + $this->assertSame($translatedMessage, $exception->getMessage()); + $this->assertSame($error['type'], $exception->getSeverity()); + $this->assertSame($error['file'], $exception->getFile()); + $this->assertSame($error['line'], $exception->getLine()); + } + + public function provideUndefinedMethodData() + { + return array( + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined method SplObjectStorage::what()', + ), + 'Attempted to call an undefined method named "what" of class "SplObjectStorage".', + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined method SplObjectStorage::walid()', + ), + "Attempted to call an undefined method named \"walid\" of class \"SplObjectStorage\".\nDid you mean to call \"valid\"?", + ), + array( + array( + 'type' => 1, + 'line' => 12, + 'file' => 'foo.php', + 'message' => 'Call to undefined method SplObjectStorage::offsetFet()', + ), + "Attempted to call an undefined method named \"offsetFet\" of class \"SplObjectStorage\".\nDid you mean to call e.g. \"offsetGet\", \"offsetSet\" or \"offsetUnset\"?", + ), + array( + array( + 'type' => 1, + 'message' => 'Call to undefined method class@anonymous::test()', + 'file' => '/home/possum/work/symfony/test.php', + 'line' => 11, + ), + 'Attempted to call an undefined method named "test" of class "class@anonymous".', + ), + ); + } +} diff --git a/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php b/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php new file mode 100644 index 00000000..9d6dbaa7 --- /dev/null +++ b/vendor/symfony/debug/Tests/Fixtures/ClassAlias.php @@ -0,0 +1,3 @@ +exception = $e; + } + + public function __toString() + { + try { + throw $this->exception; + } catch (\Exception $e) { + // Using user_error() here is on purpose so we do not forget + // that this alias also should work alongside with trigger_error(). + return user_error($e, E_USER_ERROR); + } + } +} diff --git a/vendor/symfony/debug/Tests/Fixtures/casemismatch.php b/vendor/symfony/debug/Tests/Fixtures/casemismatch.php new file mode 100644 index 00000000..691d660f --- /dev/null +++ b/vendor/symfony/debug/Tests/Fixtures/casemismatch.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +function headers_sent() +{ + return false; +} + +function header($str, $replace = true, $status = null) +{ + Tests\testHeader($str, $replace, $status); +} + +namespace Symfony\Component\Debug\Tests; + +function testHeader() +{ + static $headers = array(); + + if (!$h = func_get_args()) { + $h = $headers; + $headers = array(); + + return $h; + } + + $headers[] = func_get_args(); +} diff --git a/vendor/symfony/debug/Tests/MockExceptionHandler.php b/vendor/symfony/debug/Tests/MockExceptionHandler.php new file mode 100644 index 00000000..2d6ce564 --- /dev/null +++ b/vendor/symfony/debug/Tests/MockExceptionHandler.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\Debug\Tests; + +use Symfony\Component\Debug\ExceptionHandler; + +class MockExceptionHandler extends ExceptionHandler +{ + public $e; + + public function handle(\Exception $e) + { + $this->e = $e; + } +} diff --git a/vendor/symfony/debug/composer.json b/vendor/symfony/debug/composer.json new file mode 100644 index 00000000..6531eefd --- /dev/null +++ b/vendor/symfony/debug/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/debug", + "type": "library", + "description": "Symfony Debug Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Debug\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/debug/phpunit.xml.dist b/vendor/symfony/debug/phpunit.xml.dist new file mode 100644 index 00000000..12e58612 --- /dev/null +++ b/vendor/symfony/debug/phpunit.xml.dist @@ -0,0 +1,33 @@ + + + + + + + + + + ./Tests/ + + + ./Resources/ext/tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/event-dispatcher/.gitignore b/vendor/symfony/event-dispatcher/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/event-dispatcher/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/event-dispatcher/CHANGELOG.md b/vendor/symfony/event-dispatcher/CHANGELOG.md new file mode 100644 index 00000000..736bd849 --- /dev/null +++ b/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,37 @@ +CHANGELOG +========= + +3.3.0 +----- + + * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead. + +3.0.0 +----- + + * The method `getListenerPriority($eventName, $listener)` has been added to the + `EventDispatcherInterface`. + * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()` + and `Event::getName()` have been removed. + The event dispatcher and the event name are passed to the listener call. + +2.5.0 +----- + + * added Debug\TraceableEventDispatcher (originally in HttpKernel) + * changed Debug\TraceableEventDispatcherInterface to extend EventDispatcherInterface + * added RegisterListenersPass (originally in HttpKernel) + +2.1.0 +----- + + * added TraceableEventDispatcherInterface + * added ContainerAwareEventDispatcher + * added a reference to the EventDispatcher on the Event + * added a reference to the Event name on the event + * added fluid interface to the dispatch() method which now returns the Event + object + * added GenericEvent event class + * added the possibility for subscribers to subscribe several times for the + same event + * added ImmutableEventDispatcher diff --git a/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php new file mode 100644 index 00000000..fc7b30f9 --- /dev/null +++ b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Lazily loads listeners and subscribers from the dependency injection + * container. + * + * @author Fabien Potencier + * @author Bernhard Schussek + * @author Jordan Alliot + * + * @deprecated since 3.3, to be removed in 4.0. Use EventDispatcher with closure factories instead. + */ +class ContainerAwareEventDispatcher extends EventDispatcher +{ + /** + * The container from where services are loaded. + * + * @var ContainerInterface + */ + private $container; + + /** + * The service IDs of the event listeners and subscribers. + * + * @var array + */ + private $listenerIds = array(); + + /** + * The services registered as listeners. + * + * @var array + */ + private $listeners = array(); + + /** + * Constructor. + * + * @param ContainerInterface $container A ContainerInterface instance + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + + $class = get_class($this); + if ($this instanceof \PHPUnit_Framework_MockObject_MockObject || $this instanceof \Prophecy\Doubler\DoubleInterface) { + $class = get_parent_class($class); + } + if (__CLASS__ !== $class) { + @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); + } + } + + /** + * Adds a service as event listener. + * + * @param string $eventName Event for which the listener is added + * @param array $callback The service ID of the listener service & the method + * name that has to be called + * @param int $priority The higher this value, the earlier an event listener + * will be triggered in the chain. + * Defaults to 0. + * + * @throws \InvalidArgumentException + */ + public function addListenerService($eventName, $callback, $priority = 0) + { + @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); + + if (!is_array($callback) || 2 !== count($callback)) { + throw new \InvalidArgumentException('Expected an array("service", "method") argument'); + } + + $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); + } + + public function removeListener($eventName, $listener) + { + $this->lazyLoad($eventName); + + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) { + $key = $serviceId.'.'.$method; + if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { + unset($this->listeners[$eventName][$key]); + if (empty($this->listeners[$eventName])) { + unset($this->listeners[$eventName]); + } + unset($this->listenerIds[$eventName][$i]); + if (empty($this->listenerIds[$eventName])) { + unset($this->listenerIds[$eventName]); + } + } + } + } + + parent::removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + if (null === $eventName) { + return $this->listenerIds || $this->listeners || parent::hasListeners(); + } + + if (isset($this->listenerIds[$eventName])) { + return true; + } + + return parent::hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null === $eventName) { + foreach ($this->listenerIds as $serviceEventName => $args) { + $this->lazyLoad($serviceEventName); + } + } else { + $this->lazyLoad($eventName); + } + + return parent::getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + $this->lazyLoad($eventName); + + return parent::getListenerPriority($eventName, $listener); + } + + /** + * Adds a service as event subscriber. + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name (which must implement EventSubscriberInterface) + */ + public function addSubscriberService($serviceId, $class) + { + @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); + + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } elseif (is_string($params[0])) { + $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + public function getContainer() + { + @trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 as its class will be removed in 4.0. Inject the container or the services you need in your listeners/subscribers instead.', E_USER_DEPRECATED); + + return $this->container; + } + + /** + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + */ + protected function lazyLoad($eventName) + { + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) { + $listener = $this->container->get($serviceId); + + $key = $serviceId.'.'.$method; + if (!isset($this->listeners[$eventName][$key])) { + $this->addListener($eventName, array($listener, $method), $priority); + } elseif ($listener !== $this->listeners[$eventName][$key]) { + parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method)); + $this->addListener($eventName, array($listener, $method), $priority); + } + + $this->listeners[$eventName][$key] = $listener; + } + } + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 00000000..988cf112 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,324 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Stopwatch\Stopwatch; +use Psr\Log\LoggerInterface; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements TraceableEventDispatcherInterface +{ + protected $logger; + protected $stopwatch; + + private $called; + private $dispatcher; + private $wrappedListeners; + + /** + * Constructor. + * + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->called = array(); + $this->wrappedListeners = array(); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener) { + $listener = $wrappedListener; + unset($this->wrappedListeners[$eventName][$index]); + break; + } + } + } + + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + // we might have wrapped listeners for the event (if called while dispatching) + // in that case get the priority by wrapper + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener) { + return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); + } + } + } + + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + if (null !== $this->logger && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + + $this->preProcess($eventName); + $this->preDispatch($eventName, $event); + + $e = $this->stopwatch->start($eventName, 'section'); + + $this->dispatcher->dispatch($eventName, $event); + + if ($e->isStarted()) { + $e->stop(); + } + + $this->postDispatch($eventName, $event); + $this->postProcess($eventName); + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getCalledListeners() + { + $called = array(); + foreach ($this->called as $eventName => $listeners) { + foreach ($listeners as $listener) { + $called[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName); + } + } + + return $called; + } + + /** + * {@inheritdoc} + */ + public function getNotCalledListeners() + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e)); + } + + // unable to retrieve the uncalled listeners + return array(); + } + + $notCalled = array(); + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + $called = false; + if (isset($this->called[$eventName])) { + foreach ($this->called[$eventName] as $l) { + if ($l->getWrappedListener() === $listener) { + $called = true; + + break; + } + } + } + + if (!$called) { + if (!$listener instanceof WrappedListener) { + $listener = new WrappedListener($listener, null, $this->stopwatch, $this); + } + $notCalled[$eventName.'.'.$listener->getPretty()] = $listener->getInfo($eventName); + } + } + } + + uasort($notCalled, array($this, 'sortListenersByPriority')); + + return $notCalled; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return call_user_func_array(array($this->dispatcher, $method), $arguments); + } + + /** + * Called before dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function preDispatch($eventName, Event $event) + { + } + + /** + * Called after dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function postDispatch($eventName, Event $event) + { + } + + private function preProcess($eventName) + { + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $priority = $this->getListenerPriority($eventName, $listener); + $wrappedListener = new WrappedListener($listener, null, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $wrappedListener, $priority); + } + } + + private function postProcess($eventName) + { + unset($this->wrappedListeners[$eventName]); + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. + continue; + } + // Unwrap listener + $priority = $this->getListenerPriority($eventName, $listener); + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); + + if (null !== $this->logger) { + $context = array('event' => $eventName, 'listener' => $listener->getPretty()); + } + + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug('Notified event "{event}" to listener "{listener}".', $context); + } + + if (!isset($this->called[$eventName])) { + $this->called[$eventName] = new \SplObjectStorage(); + } + + $this->called[$eventName]->attach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context); + } + + if ($listener->stoppedPropagation()) { + if (null !== $this->logger) { + $this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context); + } + + $skipped = true; + } + } + } + + private function sortListenersByPriority($a, $b) + { + if (is_int($a['priority']) && !is_int($b['priority'])) { + return 1; + } + + if (!is_int($a['priority']) && is_int($b['priority'])) { + return -1; + } + + if ($a['priority'] === $b['priority']) { + return 0; + } + + if ($a['priority'] > $b['priority']) { + return -1; + } + + return 1; + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php new file mode 100644 index 00000000..5483e815 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Fabien Potencier + */ +interface TraceableEventDispatcherInterface extends EventDispatcherInterface +{ + /** + * Gets the called listeners. + * + * @return array An array of called listeners + */ + public function getCalledListeners(); + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + */ + public function getNotCalledListeners(); +} diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 00000000..4029883d --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * @author Fabien Potencier + */ +class WrappedListener +{ + private $listener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + private $pretty; + private $stub; + + private static $cloner; + + public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->name = $name; + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->called = false; + $this->stoppedPropagation = false; + + if (is_array($listener)) { + $this->name = is_object($listener[0]) ? get_class($listener[0]) : $listener[0]; + $this->pretty = $this->name.'::'.$listener[1]; + } elseif ($listener instanceof \Closure) { + $this->pretty = $this->name = 'closure'; + } elseif (is_string($listener)) { + $this->pretty = $this->name = $listener; + } else { + $this->name = get_class($listener); + $this->pretty = $this->name.'::__invoke'; + } + + if (null !== $name) { + $this->name = $name; + } + + if (null === self::$cloner) { + self::$cloner = class_exists(ClassStub::class) ? new VarCloner() : false; + } + } + + public function getWrappedListener() + { + return $this->listener; + } + + public function wasCalled() + { + return $this->called; + } + + public function stoppedPropagation() + { + return $this->stoppedPropagation; + } + + public function getPretty() + { + return $this->pretty; + } + + public function getInfo($eventName) + { + if (null === $this->stub) { + $this->stub = false === self::$cloner ? $this->pretty.'()' : new ClassStub($this->pretty.'()', $this->listener); + } + + return array( + 'event' => $eventName, + 'priority' => null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null, + 'pretty' => $this->pretty, + 'stub' => $this->stub, + ); + } + + public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) + { + $this->called = true; + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 00000000..50e466a3 --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + /** + * @var string + */ + protected $dispatcherService; + + /** + * @var string + */ + protected $listenerTag; + + /** + * @var string + */ + protected $subscriberTag; + + /** + * Constructor. + * + * @param string $dispatcherService Service name of the event dispatcher in processed container + * @param string $listenerTag Tag name used for listener + * @param string $subscriberTag Tag name used for subscribers + */ + public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber') + { + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + return; + } + + $definition = $container->findDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { + $def = $container->getDefinition($id); + + foreach ($events as $event) { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) { + throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback(array( + '/(?<=\b)[a-z]/i', + '/[^a-z0-9]/i', + ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + } + + $definition->addMethodCall('addListener', array($event['event'], array(new ServiceClosureArgument(new Reference($id)), $event['method']), $priority)); + } + } + + $extractingDispatcher = new ExtractingEventDispatcher(); + + foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) { + $def = $container->getDefinition($id); + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getParameterBag()->resolveValue($def->getClass()); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + + if (!is_subclass_of($class, $interface)) { + if (!class_exists($class, false)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + $container->addObjectResource($class); + + ExtractingEventDispatcher::$subscriber = $class; + $extractingDispatcher->addSubscriber($extractingDispatcher); + foreach ($extractingDispatcher->listeners as $args) { + $args[1] = array(new ServiceClosureArgument(new Reference($id)), $args[1]); + $definition->addMethodCall('addListener', $args); + } + $extractingDispatcher->listeners = array(); + } + } +} + +/** + * @internal + */ +class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface +{ + public $listeners = array(); + + public static $subscriber; + + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[] = array($eventName, $listener[1], $priority); + } + + public static function getSubscribedEvents() + { + $callback = array(self::$subscriber, 'getSubscribedEvents'); + + return $callback(); + } +} diff --git a/vendor/symfony/event-dispatcher/Event.php b/vendor/symfony/event-dispatcher/Event.php new file mode 100644 index 00000000..9c56b2f5 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Event.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +class Event +{ + /** + * @var bool Whether no further event listeners should be triggered + */ + private $propagationStopped = false; + + /** + * Returns whether further event listeners should be triggered. + * + * @see Event::stopPropagation() + * + * @return bool Whether propagation was already stopped for this event + */ + public function isPropagationStopped() + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation() + { + $this->propagationStopped = true; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 00000000..4630b01c --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -0,0 +1,236 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Jordan Alliot + * @author Nicolas Grekas + */ +class EventDispatcher implements EventDispatcherInterface +{ + private $listeners = array(); + private $sorted = array(); + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + if ($listeners = $this->getListeners($eventName)) { + $this->doDispatch($listeners, $eventName, $event); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null !== $eventName) { + if (empty($this->listeners[$eventName])) { + return array(); + } + + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach ($this->listeners as $eventName => $eventListeners) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return array_filter($this->sorted); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + if (empty($this->listeners[$eventName])) { + return; + } + + if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + foreach ($listeners as $k => $v) { + if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) { + $v[0] = $v[0](); + $this->listeners[$eventName][$priority][$k] = $v; + } + if ($v === $listener) { + return $priority; + } + } + } + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + if (null !== $eventName) { + return !empty($this->listeners[$eventName]); + } + + foreach ($this->listeners as $eventListeners) { + if ($eventListeners) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName]); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (empty($this->listeners[$eventName])) { + return; + } + + if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + foreach ($listeners as $k => $v) { + if ($v !== $listener && is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) { + $v[0] = $v[0](); + } + if ($v === $listener) { + unset($listeners[$k], $this->sorted[$eventName]); + } else { + $listeners[$k] = $v; + } + } + + if ($listeners) { + $this->listeners[$eventName][$priority] = $listeners; + } else { + unset($this->listeners[$eventName][$priority]); + } + } + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->addListener($eventName, array($subscriber, $params)); + } elseif (is_string($params[0])) { + $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_array($params) && is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, array($subscriber, $listener[0])); + } + } else { + $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0])); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param callable[] $listeners The event listeners + * @param string $eventName The name of the event to dispatch + * @param Event $event The event object to pass to the event handlers/listeners + */ + protected function doDispatch($listeners, $eventName, Event $event) + { + foreach ($listeners as $listener) { + if ($event->isPropagationStopped()) { + break; + } + call_user_func($listener, $event, $eventName, $this); + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + * + * @param string $eventName The name of the event + */ + private function sortListeners($eventName) + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = array(); + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + foreach ($listeners as $k => $listener) { + if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + $this->listeners[$eventName][$priority][$k] = $listener; + } + $this->sorted[$eventName][] = $listener; + } + } + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 00000000..08ebf340 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Bernhard Schussek + */ +interface EventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + * @param Event $event The event to pass to the event handlers/listeners + * If not supplied, an empty Event instance is created. + * + * @return Event + */ + public function dispatch($eventName, Event $event = null); + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $listener The listener + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function addListener($eventName, $listener, $priority = 0); + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events he is + * interested in and added as a listener for these events. + * + * @param EventSubscriberInterface $subscriber The subscriber + */ + public function addSubscriber(EventSubscriberInterface $subscriber); + + /** + * Removes an event listener from the specified events. + * + * @param string $eventName The event to remove a listener from + * @param callable $listener The listener to remove + */ + public function removeListener($eventName, $listener); + + /** + * Removes an event subscriber. + * + * @param EventSubscriberInterface $subscriber The subscriber + */ + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @param string $eventName The name of the event + * + * @return array The event listeners for the specified event, or all event listeners by event name + */ + public function getListeners($eventName = null); + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + * + * @param string $eventName The name of the event + * @param callable $listener The listener + * + * @return int|null The event listener priority + */ + public function getListenerPriority($eventName, $listener); + + /** + * Checks whether an event has any registered listeners. + * + * @param string $eventName The name of the event + * + * @return bool true if the specified event has any listeners, false otherwise + */ + public function hasListeners($eventName = null); +} diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php new file mode 100644 index 00000000..8af77891 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * An EventSubscriber knows himself what events he is interested in. + * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))) + * + * @return array The event names to listen to + */ + public static function getSubscribedEvents(); +} diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php new file mode 100644 index 00000000..e8e4cc05 --- /dev/null +++ b/vendor/symfony/event-dispatcher/GenericEvent.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\EventDispatcher; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + /** + * Event subject. + * + * @var mixed usually object or callable + */ + protected $subject; + + /** + * Array of arguments. + * + * @var array + */ + protected $arguments; + + /** + * Encapsulate an event with $subject and $args. + * + * @param mixed $subject The subject of the event, usually an object + * @param array $arguments Arguments to store in the event + */ + public function __construct($subject = null, array $arguments = array()) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed $subject The observer subject + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @param string $key Key + * + * @return mixed Contents of array key + * + * @throws \InvalidArgumentException If key is not found. + */ + public function getArgument($key) + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); + } + + /** + * Add argument to event. + * + * @param string $key Argument name + * @param mixed $value Value + * + * @return $this + */ + public function setArgument($key, $value) + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Set args property. + * + * @param array $args Arguments + * + * @return $this + */ + public function setArguments(array $args = array()) + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + * + * @param string $key Key of arguments array + * + * @return bool + */ + public function hasArgument($key) + { + return array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key + * + * @return mixed + * + * @throws \InvalidArgumentException If key does not exist in $this->args. + */ + public function offsetGet($key) + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set + * @param mixed $value Value + */ + public function offsetSet($key, $value) + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key + */ + public function offsetUnset($key) + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key + * + * @return bool + */ + public function offsetExists($key) + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php new file mode 100644 index 00000000..7f2be8d3 --- /dev/null +++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + /** + * The proxied dispatcher. + * + * @var EventDispatcherInterface + */ + private $dispatcher; + + /** + * Creates an unmodifiable proxy for an event dispatcher. + * + * @param EventDispatcherInterface $dispatcher The proxied event dispatcher + */ + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + return $this->dispatcher->dispatch($eventName, $event); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher/README.md b/vendor/symfony/event-dispatcher/README.md new file mode 100644 index 00000000..185c3fec --- /dev/null +++ b/vendor/symfony/event-dispatcher/README.md @@ -0,0 +1,15 @@ +EventDispatcher Component +========================= + +The EventDispatcher component provides tools that allow your application +components to communicate with each other by dispatching events and listening to +them. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/event_dispatcher/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php new file mode 100644 index 00000000..9443f216 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.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\EventDispatcher\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +abstract class AbstractEventDispatcherTest extends TestCase +{ + /* Some pseudo events */ + const preFoo = 'pre.foo'; + const postFoo = 'post.foo'; + const preBar = 'pre.bar'; + const postBar = 'post.bar'; + + /** + * @var EventDispatcher + */ + private $dispatcher; + + private $listener; + + protected function setUp() + { + $this->dispatcher = $this->createEventDispatcher(); + $this->listener = new TestEventListener(); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->listener = null; + } + + abstract protected function createEventDispatcher(); + + public function testInitialState() + { + $this->assertEquals(array(), $this->dispatcher->getListeners()); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddListener() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->assertTrue($this->dispatcher->hasListeners()); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo)); + $this->assertCount(2, $this->dispatcher->getListeners()); + } + + public function testGetListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener1->name = '1'; + $listener2->name = '2'; + $listener3->name = '3'; + + $this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10); + $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10); + $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo')); + + $expected = array( + array($listener2, 'preFoo'), + array($listener3, 'preFoo'), + array($listener1, 'preFoo'), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo')); + } + + public function testGetAllListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener4 = new TestEventListener(); + $listener5 = new TestEventListener(); + $listener6 = new TestEventListener(); + + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->addListener('post.foo', $listener4, -10); + $this->dispatcher->addListener('post.foo', $listener5); + $this->dispatcher->addListener('post.foo', $listener6, 10); + + $expected = array( + 'pre.foo' => array($listener3, $listener2, $listener1), + 'post.foo' => array($listener6, $listener5, $listener4), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners()); + } + + public function testGetListenerPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + + $this->assertSame(-10, $this->dispatcher->getListenerPriority('pre.foo', $listener1)); + $this->assertSame(0, $this->dispatcher->getListenerPriority('pre.foo', $listener2)); + $this->assertNull($this->dispatcher->getListenerPriority('pre.bar', $listener2)); + $this->assertNull($this->dispatcher->getListenerPriority('pre.foo', function () {})); + } + + public function testDispatch() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->dispatcher->dispatch(self::preFoo); + $this->assertTrue($this->listener->preFooInvoked); + $this->assertFalse($this->listener->postFooInvoked); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent')); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo)); + $event = new Event(); + $return = $this->dispatcher->dispatch(self::preFoo, $event); + $this->assertSame($event, $return); + } + + public function testDispatchForClosure() + { + $invoked = 0; + $listener = function () use (&$invoked) { + ++$invoked; + }; + $this->dispatcher->addListener('pre.foo', $listener); + $this->dispatcher->addListener('post.foo', $listener); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(1, $invoked); + } + + public function testStopEventPropagation() + { + $otherListener = new TestEventListener(); + + // postFoo() stops the propagation, so only one listener should + // be executed + // Manually set priority to enforce $this->listener to be called first + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10); + $this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo')); + $this->dispatcher->dispatch(self::postFoo); + $this->assertTrue($this->listener->postFooInvoked); + $this->assertFalse($otherListener->postFooInvoked); + } + + public function testDispatchByPriority() + { + $invoked = array(); + $listener1 = function () use (&$invoked) { + $invoked[] = '1'; + }; + $listener2 = function () use (&$invoked) { + $invoked[] = '2'; + }; + $listener3 = function () use (&$invoked) { + $invoked[] = '3'; + }; + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(array('3', '2', '1'), $invoked); + } + + public function testRemoveListener() + { + $this->dispatcher->addListener('pre.bar', $this->listener); + $this->assertTrue($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('pre.bar', $this->listener); + $this->assertFalse($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('notExists', $this->listener); + } + + public function testAddSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]); + } + + public function testAddSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertEquals('preFoo2', $listeners[0][1]); + } + + public function testRemoveSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testRemoveSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + public function testRemoveSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + public function testEventReceivesTheDispatcherInstanceAsArgument() + { + $listener = new TestWithDispatcher(); + $this->dispatcher->addListener('test', array($listener, 'foo')); + $this->assertNull($listener->name); + $this->assertNull($listener->dispatcher); + $this->dispatcher->dispatch('test'); + $this->assertEquals('test', $listener->name); + $this->assertSame($this->dispatcher, $listener->dispatcher); + } + + /** + * @see https://bugs.php.net/bug.php?id=62976 + * + * This bug affects: + * - The PHP 5.3 branch for versions < 5.3.18 + * - The PHP 5.4 branch for versions < 5.4.8 + * - The PHP 5.5 branch is not affected + */ + public function testWorkaroundForPhpBug62976() + { + $dispatcher = $this->createEventDispatcher(); + $dispatcher->addListener('bug.62976', new CallableClass()); + $dispatcher->removeListener('bug.62976', function () {}); + $this->assertTrue($dispatcher->hasListeners('bug.62976')); + } + + public function testHasListenersWhenAddedCallbackListenerIsRemoved() + { + $listener = function () {}; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertFalse($this->dispatcher->hasListeners()); + } + + public function testGetListenersWhenAddedCallbackListenerIsRemoved() + { + $listener = function () {}; + $this->dispatcher->addListener('foo', $listener); + $this->dispatcher->removeListener('foo', $listener); + $this->assertSame(array(), $this->dispatcher->getListeners()); + } + + public function testHasListenersWithoutEventsReturnsFalseAfterHasListenersWithEventHasBeenCalled() + { + $this->assertFalse($this->dispatcher->hasListeners('foo')); + $this->assertFalse($this->dispatcher->hasListeners()); + } + + public function testHasListenersIsLazy() + { + $called = 0; + $listener = array(function () use (&$called) { ++$called; }, 'onFoo'); + $this->dispatcher->addListener('foo', $listener); + $this->assertTrue($this->dispatcher->hasListeners()); + $this->assertTrue($this->dispatcher->hasListeners('foo')); + $this->assertSame(0, $called); + } + + public function testDispatchLazyListener() + { + $called = 0; + $factory = function () use (&$called) { + ++$called; + + return new TestWithDispatcher(); + }; + $this->dispatcher->addListener('foo', array($factory, 'foo')); + $this->assertSame(0, $called); + $this->dispatcher->dispatch('foo', new Event()); + $this->dispatcher->dispatch('foo', new Event()); + $this->assertSame(1, $called); + } + + public function testRemoveFindsLazyListeners() + { + $test = new TestWithDispatcher(); + $factory = function () use ($test) { return $test; }; + + $this->dispatcher->addListener('foo', array($factory, 'foo')); + $this->assertTrue($this->dispatcher->hasListeners('foo')); + $this->dispatcher->removeListener('foo', array($test, 'foo')); + $this->assertFalse($this->dispatcher->hasListeners('foo')); + + $this->dispatcher->addListener('foo', array($test, 'foo')); + $this->assertTrue($this->dispatcher->hasListeners('foo')); + $this->dispatcher->removeListener('foo', array($factory, 'foo')); + $this->assertFalse($this->dispatcher->hasListeners('foo')); + } + + public function testPriorityFindsLazyListeners() + { + $test = new TestWithDispatcher(); + $factory = function () use ($test) { return $test; }; + + $this->dispatcher->addListener('foo', array($factory, 'foo'), 3); + $this->assertSame(3, $this->dispatcher->getListenerPriority('foo', array($test, 'foo'))); + $this->dispatcher->removeListener('foo', array($factory, 'foo')); + + $this->dispatcher->addListener('foo', array($test, 'foo'), 5); + $this->assertSame(5, $this->dispatcher->getListenerPriority('foo', array($factory, 'foo'))); + } + + public function testGetLazyListeners() + { + $test = new TestWithDispatcher(); + $factory = function () use ($test) { return $test; }; + + $this->dispatcher->addListener('foo', array($factory, 'foo'), 3); + $this->assertSame(array(array($test, 'foo')), $this->dispatcher->getListeners('foo')); + + $this->dispatcher->removeListener('foo', array($test, 'foo')); + $this->dispatcher->addListener('bar', array($factory, 'foo'), 3); + $this->assertSame(array('bar' => array(array($test, 'foo'))), $this->dispatcher->getListeners()); + } +} + +class CallableClass +{ + public function __invoke() + { + } +} + +class TestEventListener +{ + public $preFooInvoked = false; + public $postFooInvoked = false; + + /* Listener methods */ + + public function preFoo(Event $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(Event $e) + { + $this->postFooInvoked = true; + + $e->stopPropagation(); + } +} + +class TestWithDispatcher +{ + public $name; + public $dispatcher; + + public function foo(Event $e, $name, $dispatcher) + { + $this->name = $name; + $this->dispatcher = $dispatcher; + } +} + +class TestEventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo'); + } +} + +class TestEventSubscriberWithPriorities implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'pre.foo' => array('preFoo', 10), + 'post.foo' => array('postFoo'), + ); + } +} + +class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => array( + array('preFoo1'), + array('preFoo2', 10), + )); + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php new file mode 100644 index 00000000..18055614 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * @group legacy + */ +class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + $container = new Container(); + + return new ContainerAwareEventDispatcher($container); + } + + public function testAddAListenerService() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testAddASubscriberService() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\SubscriberService')->getMock(); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $service + ->expects($this->once()) + ->method('onEventWithPriority') + ->with($event) + ; + + $service + ->expects($this->once()) + ->method('onEventNested') + ->with($event) + ; + + $container = new Container(); + $container->set('service.subscriber', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $dispatcher->dispatch('onEvent', $event); + $dispatcher->dispatch('onEventWithPriority', $event); + $dispatcher->dispatch('onEventNested', $event); + } + + public function testPreventDuplicateListenerService() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testHasListenersOnLazyLoad() + { + $event = new Event(); + + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $this->assertTrue($dispatcher->hasListeners()); + + if ($dispatcher->hasListeners('onEvent')) { + $dispatcher->dispatch('onEvent'); + } + } + + public function testGetListenersOnLazyLoad() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $listeners = $dispatcher->getListeners(); + + $this->assertTrue(isset($listeners['onEvent'])); + + $this->assertCount(1, $dispatcher->getListeners('onEvent')); + } + + public function testRemoveAfterDispatch() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', new Event()); + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } + + public function testRemoveBeforeDispatch() + { + $service = $this->getMockBuilder('Symfony\Component\EventDispatcher\Tests\Service')->getMock(); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } +} + +class Service +{ + public function onEvent(Event $e) + { + } +} + +class SubscriberService implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'onEvent' => 'onEvent', + 'onEventWithPriority' => array('onEventWithPriority', 10), + 'onEventNested' => array(array('onEventNested')), + ); + } + + public function onEvent(Event $e) + { + } + + public function onEventWithPriority(Event $e) + { + } + + public function onEventNested(Event $e) + { + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php new file mode 100644 index 00000000..a1cf6708 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\Stopwatch\Stopwatch; + +class TraceableEventDispatcherTest extends TestCase +{ + public function testAddRemoveListener() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () {}); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame($listener, $listeners[0]); + + $tdispatcher->removeListener('foo', $listener); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + public function testGetListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () {}); + $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo')); + } + + public function testHasListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $this->assertFalse($dispatcher->hasListeners('foo')); + $this->assertFalse($tdispatcher->hasListeners('foo')); + + $tdispatcher->addListener('foo', $listener = function () {}); + $this->assertTrue($dispatcher->hasListeners('foo')); + $this->assertTrue($tdispatcher->hasListeners('foo')); + } + + public function testGetListenerPriority() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', function () {}, 123); + + $listeners = $dispatcher->getListeners('foo'); + $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0])); + + // Verify that priority is preserved when listener is removed and re-added + // in preProcess() and postProcess(). + $tdispatcher->dispatch('foo', new Event()); + $listeners = $dispatcher->getListeners('foo'); + $this->assertSame(123, $tdispatcher->getListenerPriority('foo', $listeners[0])); + } + + public function testGetListenerPriorityWhileDispatching() + { + $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $priorityWhileDispatching = null; + + $listener = function () use ($tdispatcher, &$priorityWhileDispatching, &$listener) { + $priorityWhileDispatching = $tdispatcher->getListenerPriority('bar', $listener); + }; + + $tdispatcher->addListener('bar', $listener, 5); + $tdispatcher->dispatch('bar'); + $this->assertSame(5, $priorityWhileDispatching); + } + + public function testAddRemoveSubscriber() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $subscriber = new EventSubscriber(); + + $tdispatcher->addSubscriber($subscriber); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame(array($subscriber, 'call'), $listeners[0]); + + $tdispatcher->removeSubscriber($subscriber); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + public function testGetCalledListeners() + { + $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $tdispatcher->addListener('foo', function () {}, 5); + + $listeners = $tdispatcher->getNotCalledListeners(); + $this->assertArrayHasKey('stub', $listeners['foo.closure']); + unset($listeners['foo.closure']['stub']); + $this->assertEquals(array(), $tdispatcher->getCalledListeners()); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners); + + $tdispatcher->dispatch('foo'); + + $listeners = $tdispatcher->getCalledListeners(); + $this->assertArrayHasKey('stub', $listeners['foo.closure']); + unset($listeners['foo.closure']['stub']); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners); + $this->assertEquals(array(), $tdispatcher->getNotCalledListeners()); + } + + public function testGetCalledListenersNested() + { + $tdispatcher = null; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('foo', function (Event $event, $eventName, $dispatcher) use (&$tdispatcher) { + $tdispatcher = $dispatcher; + $dispatcher->dispatch('bar'); + }); + $dispatcher->addListener('bar', function (Event $event) {}); + $dispatcher->dispatch('foo'); + $this->assertSame($dispatcher, $tdispatcher); + $this->assertCount(2, $dispatcher->getCalledListeners()); + } + + public function testLogger() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function () {}); + $tdispatcher->addListener('foo', $listener2 = function () {}); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure')); + $logger->expects($this->at(1))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure')); + + $tdispatcher->dispatch('foo'); + } + + public function testLoggerWithStoppedEvent() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); }); + $tdispatcher->addListener('foo', $listener2 = function () {}); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "{event}" to listener "{listener}".', array('event' => 'foo', 'listener' => 'closure')); + $logger->expects($this->at(1))->method('debug')->with('Listener "{listener}" stopped propagation of the event "{event}".', array('event' => 'foo', 'listener' => 'closure')); + $logger->expects($this->at(2))->method('debug')->with('Listener "{listener}" was not called for event "{event}".', array('event' => 'foo', 'listener' => 'closure')); + + $tdispatcher->dispatch('foo'); + } + + public function testDispatchCallListeners() + { + $called = array(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10); + $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20); + + $tdispatcher->dispatch('foo'); + + $this->assertSame(array('foo2', 'foo1'), $called); + } + + public function testDispatchNested() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $loop = 1; + $dispatchedEvents = 0; + $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) { + ++$loop; + if (2 == $loop) { + $dispatcher->dispatch('foo'); + } + }); + $dispatcher->addListener('foo', function () use (&$dispatchedEvents) { + ++$dispatchedEvents; + }); + + $dispatcher->dispatch('foo'); + + $this->assertSame(2, $dispatchedEvents); + } + + public function testDispatchReusedEventNested() + { + $nestedCall = false; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('foo', function (Event $e) use ($dispatcher) { + $dispatcher->dispatch('bar', $e); + }); + $dispatcher->addListener('bar', function (Event $e) use (&$nestedCall) { + $nestedCall = true; + }); + + $this->assertFalse($nestedCall); + $dispatcher->dispatch('foo'); + $this->assertTrue($nestedCall); + } + + public function testListenerCanRemoveItselfWhenExecuted() + { + $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $listener1 = function ($event, $eventName, EventDispatcherInterface $dispatcher) use (&$listener1) { + $dispatcher->removeListener('foo', $listener1); + }; + $eventDispatcher->addListener('foo', $listener1); + $eventDispatcher->addListener('foo', function () {}); + $eventDispatcher->dispatch('foo'); + + $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed'); + } +} + +class EventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('foo' => 'call'); + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php new file mode 100644 index 00000000..d46d8c59 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; + +class RegisterListenersPassTest extends TestCase +{ + /** + * Tests that event subscribers not implementing EventSubscriberInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testEventSubscriberWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('stdClass')); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + public function testValidEventSubscriber() + { + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $builder->expects($this->atLeastOnce()) + ->method('findDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" tagged "kernel.event_listener" must not be abstract. + */ + public function testAbstractEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" tagged "kernel.event_subscriber" must not be abstract. + */ + public function testAbstractEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + public function testEventSubscriberResolvableClassName() + { + $container = new ContainerBuilder(); + + $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expectedCalls = array( + array( + 'addListener', + array( + 'event', + array(new ServiceClosureArgument(new Reference('foo')), 'onEvent'), + 0, + ), + ), + ); + $this->assertEquals($expectedCalls, $definition->getMethodCalls()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class" + */ + public function testEventSubscriberUnresolvableClassName() + { + $container = new ContainerBuilder(); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } +} + +class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'event' => 'onEvent', + ); + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php new file mode 100644 index 00000000..5faa5c8b --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\EventDispatcher; + +class EventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + return new EventDispatcher(); + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/EventTest.php b/vendor/symfony/event-dispatcher/Tests/EventTest.php new file mode 100644 index 00000000..5be2ea09 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/EventTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\Event; + +/** + * Test class for Event. + */ +class EventTest extends TestCase +{ + /** + * @var \Symfony\Component\EventDispatcher\Event + */ + protected $event; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + $this->event = new Event(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + $this->event = null; + } + + public function testIsPropagationStopped() + { + $this->assertFalse($this->event->isPropagationStopped()); + } + + public function testStopPropagationAndIsPropagationStopped() + { + $this->event->stopPropagation(); + $this->assertTrue($this->event->isPropagationStopped()); + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php new file mode 100644 index 00000000..c84d3ac2 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\GenericEvent; + +/** + * Test class for Event. + */ +class GenericEventTest extends TestCase +{ + /** + * @var GenericEvent + */ + private $event; + + private $subject; + + /** + * Prepares the environment before running a test. + */ + protected function setUp() + { + parent::setUp(); + + $this->subject = new \stdClass(); + $this->event = new GenericEvent($this->subject, array('name' => 'Event')); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->subject = null; + $this->event = null; + + parent::tearDown(); + } + + public function testConstruct() + { + $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event'))); + } + + /** + * Tests Event->getArgs(). + */ + public function testGetArguments() + { + // test getting all + $this->assertSame(array('name' => 'Event'), $this->event->getArguments()); + } + + public function testSetArguments() + { + $result = $this->event->setArguments(array('foo' => 'bar')); + $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event); + $this->assertSame($this->event, $result); + } + + public function testSetArgument() + { + $result = $this->event->setArgument('foo2', 'bar2'); + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + $this->assertEquals($this->event, $result); + } + + public function testGetArgument() + { + // test getting key + $this->assertEquals('Event', $this->event->getArgument('name')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetArgException() + { + $this->event->getArgument('nameNotExist'); + } + + public function testOffsetGet() + { + // test getting key + $this->assertEquals('Event', $this->event['name']); + + // test getting invalid arg + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $this->assertFalse($this->event['nameNotExist']); + } + + public function testOffsetSet() + { + $this->event['foo2'] = 'bar2'; + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + } + + public function testOffsetUnset() + { + unset($this->event['name']); + $this->assertAttributeSame(array(), 'arguments', $this->event); + } + + public function testOffsetIsset() + { + $this->assertTrue(isset($this->event['name'])); + $this->assertFalse(isset($this->event['nameNotExist'])); + } + + public function testHasArgument() + { + $this->assertTrue($this->event->hasArgument('name')); + $this->assertFalse($this->event->hasArgument('nameNotExist')); + } + + public function testGetSubject() + { + $this->assertSame($this->subject, $this->event->getSubject()); + } + + public function testHasIterator() + { + $data = array(); + foreach ($this->event as $key => $value) { + $data[$key] = $value; + } + $this->assertEquals(array('name' => 'Event'), $data); + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.php new file mode 100644 index 00000000..04f2861e --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.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\EventDispatcher\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\ImmutableEventDispatcher; + +/** + * @author Bernhard Schussek + */ +class ImmutableEventDispatcherTest extends TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $innerDispatcher; + + /** + * @var ImmutableEventDispatcher + */ + private $dispatcher; + + protected function setUp() + { + $this->innerDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher); + } + + public function testDispatchDelegates() + { + $event = new Event(); + + $this->innerDispatcher->expects($this->once()) + ->method('dispatch') + ->with('event', $event) + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->dispatch('event', $event)); + } + + public function testGetListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('getListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->getListeners('event')); + } + + public function testHasListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('hasListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->hasListeners('event')); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddListenerDisallowed() + { + $this->dispatcher->addListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddSubscriberDisallowed() + { + $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock(); + + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveListenerDisallowed() + { + $this->dispatcher->removeListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveSubscriberDisallowed() + { + $subscriber = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventSubscriberInterface')->getMock(); + + $this->dispatcher->removeSubscriber($subscriber); + } +} diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json new file mode 100644 index 00000000..faa0429e --- /dev/null +++ b/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,47 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Symfony EventDispatcher Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/event-dispatcher/phpunit.xml.dist b/vendor/symfony/event-dispatcher/phpunit.xml.dist new file mode 100644 index 00000000..b3ad1bdf --- /dev/null +++ b/vendor/symfony/event-dispatcher/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/http-foundation/.gitignore b/vendor/symfony/http-foundation/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/http-foundation/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/http-foundation/AcceptHeader.php b/vendor/symfony/http-foundation/AcceptHeader.php new file mode 100644 index 00000000..2aa91dc4 --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeader.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\HttpFoundation; + +/** + * Represents an Accept-* header. + * + * An accept header is compound with a list of items, + * sorted by descending quality. + * + * @author Jean-François Simon + */ +class AcceptHeader +{ + /** + * @var AcceptHeaderItem[] + */ + private $items = array(); + + /** + * @var bool + */ + private $sorted = true; + + /** + * Constructor. + * + * @param AcceptHeaderItem[] $items + */ + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + + /** + * Builds an AcceptHeader instance from a string. + * + * @param string $headerValue + * + * @return self + */ + public static function fromString($headerValue) + { + $index = 0; + + return new self(array_map(function ($itemValue) use (&$index) { + $item = AcceptHeaderItem::fromString($itemValue); + $item->setIndex($index++); + + return $item; + }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE))); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Tests if header has given value. + * + * @param string $value + * + * @return bool + */ + public function has($value) + { + return isset($this->items[$value]); + } + + /** + * Returns given value's item, if exists. + * + * @param string $value + * + * @return AcceptHeaderItem|null + */ + public function get($value) + { + return isset($this->items[$value]) ? $this->items[$value] : null; + } + + /** + * Adds an item. + * + * @param AcceptHeaderItem $item + * + * @return $this + */ + public function add(AcceptHeaderItem $item) + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + + return $this; + } + + /** + * Returns all items. + * + * @return AcceptHeaderItem[] + */ + public function all() + { + $this->sort(); + + return $this->items; + } + + /** + * Filters items on their value using given regex. + * + * @param string $pattern + * + * @return self + */ + public function filter($pattern) + { + return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { + return preg_match($pattern, $item->getValue()); + })); + } + + /** + * Returns first item. + * + * @return AcceptHeaderItem|null + */ + public function first() + { + $this->sort(); + + return !empty($this->items) ? reset($this->items) : null; + } + + /** + * Sorts items by descending quality. + */ + private function sort() + { + if (!$this->sorted) { + uasort($this->items, function ($a, $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + + return $qA > $qB ? -1 : 1; + }); + + $this->sorted = true; + } + } +} diff --git a/vendor/symfony/http-foundation/AcceptHeaderItem.php b/vendor/symfony/http-foundation/AcceptHeaderItem.php new file mode 100644 index 00000000..fb54b493 --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeaderItem.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header item. + * + * @author Jean-François Simon + */ +class AcceptHeaderItem +{ + /** + * @var string + */ + private $value; + + /** + * @var float + */ + private $quality = 1.0; + + /** + * @var int + */ + private $index = 0; + + /** + * @var array + */ + private $attributes = array(); + + /** + * Constructor. + * + * @param string $value + * @param array $attributes + */ + public function __construct($value, array $attributes = array()) + { + $this->value = $value; + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + } + + /** + * Builds an AcceptHeaderInstance instance from a string. + * + * @param string $itemValue + * + * @return self + */ + public static function fromString($itemValue) + { + $bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $value = array_shift($bits); + $attributes = array(); + + $lastNullAttribute = null; + foreach ($bits as $bit) { + if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ($start === '"' || $start === '\'')) { + $attributes[$lastNullAttribute] = substr($bit, 1, -1); + } elseif ('=' === $end) { + $lastNullAttribute = $bit = substr($bit, 0, -1); + $attributes[$bit] = null; + } else { + $parts = explode('=', $bit); + $attributes[$parts[0]] = isset($parts[1]) && strlen($parts[1]) > 0 ? $parts[1] : ''; + } + } + + return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ($start === '"' || $start === '\'') ? substr($value, 1, -1) : $value, $attributes); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); + if (count($this->attributes) > 0) { + $string .= ';'.implode(';', array_map(function ($name, $value) { + return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value); + }, array_keys($this->attributes), $this->attributes)); + } + + return $string; + } + + /** + * Set the item value. + * + * @param string $value + * + * @return $this + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * Returns the item value. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the item quality. + * + * @param float $quality + * + * @return $this + */ + public function setQuality($quality) + { + $this->quality = $quality; + + return $this; + } + + /** + * Returns the item quality. + * + * @return float + */ + public function getQuality() + { + return $this->quality; + } + + /** + * Set the item index. + * + * @param int $index + * + * @return $this + */ + public function setIndex($index) + { + $this->index = $index; + + return $this; + } + + /** + * Returns the item index. + * + * @return int + */ + public function getIndex() + { + return $this->index; + } + + /** + * Tests if an attribute exists. + * + * @param string $name + * + * @return bool + */ + public function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + + /** + * Returns an attribute by its name. + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * Returns all attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set an attribute. + * + * @param string $name + * @param string $value + * + * @return $this + */ + public function setAttribute($name, $value) + { + if ('q' === $name) { + $this->quality = (float) $value; + } else { + $this->attributes[$name] = (string) $value; + } + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/ApacheRequest.php b/vendor/symfony/http-foundation/ApacheRequest.php new file mode 100644 index 00000000..84803eba --- /dev/null +++ b/vendor/symfony/http-foundation/ApacheRequest.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\HttpFoundation; + +/** + * Request represents an HTTP request from an Apache server. + * + * @author Fabien Potencier + */ +class ApacheRequest extends Request +{ + /** + * {@inheritdoc} + */ + protected function prepareRequestUri() + { + return $this->server->get('REQUEST_URI'); + } + + /** + * {@inheritdoc} + */ + protected function prepareBaseUrl() + { + $baseUrl = $this->server->get('SCRIPT_NAME'); + + if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) { + // assume mod_rewrite + return rtrim(dirname($baseUrl), '/\\'); + } + + return $baseUrl; + } +} diff --git a/vendor/symfony/http-foundation/BinaryFileResponse.php b/vendor/symfony/http-foundation/BinaryFileResponse.php new file mode 100644 index 00000000..5f18aa93 --- /dev/null +++ b/vendor/symfony/http-foundation/BinaryFileResponse.php @@ -0,0 +1,361 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\Exception\FileException; + +/** + * BinaryFileResponse represents an HTTP response delivering a file. + * + * @author Niklas Fiekas + * @author stealth35 + * @author Igor Wiedler + * @author Jordan Alliot + * @author Sergey Linnik + */ +class BinaryFileResponse extends Response +{ + protected static $trustXSendfileTypeHeader = false; + + /** + * @var File + */ + protected $file; + protected $offset; + protected $maxlen; + protected $deleteFileAfterSend = false; + + /** + * Constructor. + * + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + */ + public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + parent::__construct(null, $status, $headers); + + $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified); + + if ($public) { + $this->setPublic(); + } + } + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + * + * @return static + */ + public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); + } + + /** + * Sets the file to stream. + * + * @param \SplFileInfo|string $file The file to stream + * @param string $contentDisposition + * @param bool $autoEtag + * @param bool $autoLastModified + * + * @return $this + * + * @throws FileException + */ + public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + if (!$file instanceof File) { + if ($file instanceof \SplFileInfo) { + $file = new File($file->getPathname()); + } else { + $file = new File((string) $file); + } + } + + if (!$file->isReadable()) { + throw new FileException('File must be readable.'); + } + + $this->file = $file; + + if ($autoEtag) { + $this->setAutoEtag(); + } + + if ($autoLastModified) { + $this->setAutoLastModified(); + } + + if ($contentDisposition) { + $this->setContentDisposition($contentDisposition); + } + + return $this; + } + + /** + * Gets the file. + * + * @return File The file to stream + */ + public function getFile() + { + return $this->file; + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + */ + public function setAutoLastModified() + { + $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + */ + public function setAutoEtag() + { + $this->setEtag(sha1_file($this->file->getPathname())); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return $this + */ + public function setContentDisposition($disposition, $filename = '', $filenameFallback = '') + { + if ($filename === '') { + $filename = $this->file->getFilename(); + } + + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) { + $encoding = mb_detect_encoding($filename, null, true); + + for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { + $char = mb_substr($filename, $i, 1, $encoding); + + if ('%' === $char || ord($char) < 32 || ord($char) > 126) { + $filenameFallback .= '_'; + } else { + $filenameFallback .= $char; + } + } + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function prepare(Request $request) + { + if (!$this->headers->has('Content-Type')) { + $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + } + + if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + $this->ensureIEOverSSLCompatibility($request); + + $this->offset = 0; + $this->maxlen = -1; + + if (false === $fileSize = $this->file->getSize()) { + return $this; + } + $this->headers->set('Content-Length', $fileSize); + + if (!$this->headers->has('Accept-Ranges')) { + // Only accept ranges on safe HTTP methods + $this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none'); + } + + if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { + // Use X-Sendfile, do not send any content. + $type = $request->headers->get('X-Sendfile-Type'); + $path = $this->file->getRealPath(); + // Fall back to scheme://path for stream wrapped locations. + if (false === $path) { + $path = $this->file->getPathname(); + } + if (strtolower($type) === 'x-accel-redirect') { + // Do X-Accel-Mapping substitutions. + // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect + foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) { + $mapping = explode('=', $mapping, 2); + + if (2 === count($mapping)) { + $pathPrefix = trim($mapping[0]); + $location = trim($mapping[1]); + + if (substr($path, 0, strlen($pathPrefix)) === $pathPrefix) { + $path = $location.substr($path, strlen($pathPrefix)); + break; + } + } + } + } + $this->headers->set($type, $path); + $this->maxlen = 0; + } elseif ($request->headers->has('Range')) { + // Process the range headers. + if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { + $range = $request->headers->get('Range'); + + list($start, $end) = explode('-', substr($range, 6), 2) + array(0); + + $end = ('' === $end) ? $fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $fileSize - $end; + $end = $fileSize - 1; + } else { + $start = (int) $start; + } + + if ($start <= $end) { + if ($start < 0 || $end > $fileSize - 1) { + $this->setStatusCode(416); + $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize)); + } elseif ($start !== 0 || $end !== $fileSize - 1) { + $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; + $this->offset = $start; + + $this->setStatusCode(206); + $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); + $this->headers->set('Content-Length', $end - $start + 1); + } + } + } + } + + return $this; + } + + private function hasValidIfRangeHeader($header) + { + if ($this->getEtag() === $header) { + return true; + } + + if (null === $lastModified = $this->getLastModified()) { + return false; + } + + return $lastModified->format('D, d M Y H:i:s').' GMT' === $header; + } + + /** + * Sends the file. + * + * {@inheritdoc} + */ + public function sendContent() + { + if (!$this->isSuccessful()) { + return parent::sendContent(); + } + + if (0 === $this->maxlen) { + return $this; + } + + $out = fopen('php://output', 'wb'); + $file = fopen($this->file->getPathname(), 'rb'); + + stream_copy_to_stream($file, $out, $this->maxlen, $this->offset); + + fclose($out); + fclose($file); + + if ($this->deleteFileAfterSend) { + unlink($this->file->getPathname()); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent($content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); + } + } + + /** + * {@inheritdoc} + * + * @return false + */ + public function getContent() + { + return false; + } + + /** + * Trust X-Sendfile-Type header. + */ + public static function trustXSendfileTypeHeader() + { + self::$trustXSendfileTypeHeader = true; + } + + /** + * If this is set to true, the file will be unlinked after the request is send + * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. + * + * @param bool $shouldDelete + * + * @return $this + */ + public function deleteFileAfterSend($shouldDelete) + { + $this->deleteFileAfterSend = $shouldDelete; + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/CHANGELOG.md b/vendor/symfony/http-foundation/CHANGELOG.md new file mode 100644 index 00000000..e1fdf77b --- /dev/null +++ b/vendor/symfony/http-foundation/CHANGELOG.md @@ -0,0 +1,149 @@ +CHANGELOG +========= + +3.3.0 +----- + + * the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument, + see http://symfony.com/doc/current/components/http_foundation/trusting_proxies.html for more info, + * deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods, + * added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown, + disabling `Range` and `Content-Length` handling, switching to chunked encoding instead + * added the `Cookie::fromString()` method that allows to create a cookie from a + raw header string + +3.1.0 +----- + + * Added support for creating `JsonResponse` with a string of JSON data + +3.0.0 +----- + + * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY" + +2.8.0 +----- + + * Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and + will be removed in 3.0. + +2.6.0 +----- + + * PdoSessionHandler changes + - implemented different session locking strategies to prevent loss of data by concurrent access to the same session + - [BC BREAK] save session data in a binary column without base64_encode + - [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session + - implemented lazy connections that are only opened when a session is used by either passing a dsn string + explicitly or falling back to session.save_path ini setting + - added a createTable method that initializes a correctly defined table depending on the database vendor + +2.5.0 +----- + + * added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation + of the options used while encoding data to JSON format. + +2.4.0 +----- + + * added RequestStack + * added Request::getEncodings() + * added accessors methods to session handlers + +2.3.0 +----- + + * added support for ranges of IPs in trusted proxies + * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode) + * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` + to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases + to verify that Exceptions are properly thrown when the PDO queries fail. + +2.2.0 +----- + + * fixed the Request::create() precedence (URI information always take precedence now) + * added Request::getTrustedProxies() + * deprecated Request::isProxyTrusted() + * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects + * added a IpUtils class to check if an IP belongs to a CIDR + * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) + * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to + enable it, and Request::getHttpMethodParameterOverride() to check if it is supported) + * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3 + * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3 + +2.1.0 +----- + + * added Request::getSchemeAndHttpHost() and Request::getUserInfo() + * added a fluent interface to the Response class + * added Request::isProxyTrusted() + * added JsonResponse + * added a getTargetUrl method to RedirectResponse + * added support for streamed responses + * made Response::prepare() method the place to enforce HTTP specification + * [BC BREAK] moved management of the locale from the Session class to the Request class + * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter() + * made FileBinaryMimeTypeGuesser command configurable + * added Request::getUser() and Request::getPassword() + * added support for the PATCH method in Request + * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3 + * added ResponseHeaderBag::makeDisposition() (implements RFC 6266) + * made mimetype to extension conversion configurable + * [BC BREAK] Moved all session related classes and interfaces into own namespace, as + `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly. + Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`. + * SessionHandlers must implement `\SessionHandlerInterface` or extend from the + `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class. + * Added internal storage driver proxy mechanism for forward compatibility with + PHP 5.4 `\SessionHandler` class. + * Added session handlers for custom Memcache, Memcached and Null session save handlers. + * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`. + * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and + `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class + is a mediator for the session storage internals including the session handlers + which do the real work of participating in the internal PHP session workflow. + * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit + and functional testing without starting real PHP sessions. Removed + `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit + tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage` + for functional tests. These do not interact with global session ini + configuration values, session functions or `$_SESSION` superglobal. This means + they can be configured directly allowing multiple instances to work without + conflicting in the same PHP process. + * [BC BREAK] Removed the `close()` method from the `Session` class, as this is + now redundant. + * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()` + `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead + which returns a `FlashBagInterface`. + * `Session->clear()` now only clears session attributes as before it cleared + flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now. + * Session data is now managed by `SessionBagInterface` to better encapsulate + session data. + * Refactored session attribute and flash messages system to their own + `SessionBagInterface` implementations. + * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This + implementation is ESI compatible. + * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire + behaviour of messages auto expiring after one page page load. Messages must + be retrieved by `get()` or `all()`. + * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate + attributes storage behaviour from 2.0.x (default). + * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for + namespace session attributes. + * Flash API can stores messages in an array so there may be multiple messages + per flash type. The old `Session` class API remains without BC break as it + will allow single messages as before. + * Added basic session meta-data to the session to record session create time, + last updated time, and the lifetime of the session cookie that was provided + to the client. + * Request::getClientIp() method doesn't take a parameter anymore but bases + itself on the trustProxy parameter. + * Added isMethod() to Request object. + * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of + a `Request` now all return a raw value (vs a urldecoded value before). Any call + to one of these methods must be checked and wrapped in a `rawurldecode()` if + needed. diff --git a/vendor/symfony/http-foundation/Cookie.php b/vendor/symfony/http-foundation/Cookie.php new file mode 100644 index 00000000..2ac90268 --- /dev/null +++ b/vendor/symfony/http-foundation/Cookie.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents a cookie. + * + * @author Johannes M. Schmitt + */ +class Cookie +{ + protected $name; + protected $value; + protected $domain; + protected $expire; + protected $path; + protected $secure; + protected $httpOnly; + private $raw; + private $sameSite; + + const SAMESITE_LAX = 'lax'; + const SAMESITE_STRICT = 'strict'; + + /** + * Creates cookie from raw header string. + * + * @param string $cookie + * @param bool $decode + * + * @return static + */ + public static function fromString($cookie, $decode = false) + { + $data = array( + 'expires' => 0, + 'path' => '/', + 'domain' => null, + 'secure' => false, + 'httponly' => true, + 'raw' => !$decode, + 'samesite' => null, + ); + foreach (explode(';', $cookie) as $part) { + if (false === strpos($part, '=')) { + $key = trim($part); + $value = true; + } else { + list($key, $value) = explode('=', trim($part), 2); + $key = trim($key); + $value = trim($value); + } + if (!isset($data['name'])) { + $data['name'] = $decode ? urldecode($key) : $key; + $data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value); + continue; + } + switch ($key = strtolower($key)) { + case 'name': + case 'value': + break; + case 'max-age': + $data['expires'] = time() + (int) $value; + break; + default: + $data[$key] = $value; + break; + } + } + + return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); + } + + /** + * Constructor. + * + * @param string $name The name of the cookie + * @param string|null $value The value of the cookie + * @param int|string|\DateTimeInterface $expire The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string|null $domain The domain that the cookie is available to + * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client + * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol + * @param bool $raw Whether the cookie value should be sent with no url encoding + * @param string|null $sameSite Whether the cookie will be available for cross-site requests + * + * @throws \InvalidArgumentException + */ + public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null) + { + // from PHP source code + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + + if (empty($name)) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + + // convert expiration time to a Unix timestamp + if ($expire instanceof \DateTimeInterface) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + + if (false === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = 0 < $expire ? (int) $expire : 0; + $this->path = empty($path) ? '/' : $path; + $this->secure = (bool) $secure; + $this->httpOnly = (bool) $httpOnly; + $this->raw = (bool) $raw; + + if (!in_array($sameSite, array(self::SAMESITE_LAX, self::SAMESITE_STRICT, null), true)) { + throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); + } + + $this->sameSite = $sameSite; + } + + /** + * Returns the cookie as a string. + * + * @return string The cookie + */ + public function __toString() + { + $str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001'; + } else { + $str .= $this->isRaw() ? $this->getValue() : urlencode($this->getValue()); + + if (0 !== $this->getExpiresTime()) { + $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; max-age='.$this->getMaxAge(); + } + } + + if ($this->getPath()) { + $str .= '; path='.$this->getPath(); + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if (true === $this->isSecure()) { + $str .= '; secure'; + } + + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + + if (null !== $this->getSameSite()) { + $str .= '; samesite='.$this->getSameSite(); + } + + return $str; + } + + /** + * Gets the name of the cookie. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string|null + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + * + * @return string|null + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + * + * @return int + */ + public function getExpiresTime() + { + return $this->expire; + } + + /** + * Gets the max-age attribute. + * + * @return int + */ + public function getMaxAge() + { + return 0 !== $this->expire ? $this->expire - time() : 0; + } + + /** + * Gets the path on the server in which the cookie will be available on. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. + * + * @return bool + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Checks whether the cookie will be made accessible only through the HTTP protocol. + * + * @return bool + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Whether this cookie is about to be cleared. + * + * @return bool + */ + public function isCleared() + { + return $this->expire < time(); + } + + /** + * Checks if the cookie value should be sent with no url encoding. + * + * @return bool + */ + public function isRaw() + { + return $this->raw; + } + + /** + * Gets the SameSite attribute. + * + * @return string|null + */ + public function getSameSite() + { + return $this->sameSite; + } +} diff --git a/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php new file mode 100644 index 00000000..5fcf5b42 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * The HTTP request contains headers with conflicting information. + * + * @author Magnus Nordlander + */ +class ConflictingHeadersException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php new file mode 100644 index 00000000..478d0dc7 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/RequestExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Interface for Request exceptions. + * + * Exceptions implementing this interface should trigger an HTTP 400 response in the application code. + */ +interface RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php new file mode 100644 index 00000000..ae7a5f13 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/SuspiciousOperationException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user has performed an operation that should be considered + * suspicious from a security perspective. + */ +class SuspiciousOperationException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php new file mode 100644 index 00000000..e9c8441c --- /dev/null +++ b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; + +/** + * ExpressionRequestMatcher uses an expression to match a Request. + * + * @author Fabien Potencier + */ +class ExpressionRequestMatcher extends RequestMatcher +{ + private $language; + private $expression; + + public function setExpression(ExpressionLanguage $language, $expression) + { + $this->language = $language; + $this->expression = $expression; + } + + public function matches(Request $request) + { + if (!$this->language) { + throw new \LogicException('Unable to match the request as the expression language is not available.'); + } + + return $this->language->evaluate($this->expression, array( + 'request' => $request, + 'method' => $request->getMethod(), + 'path' => rawurldecode($request->getPathInfo()), + 'host' => $request->getHost(), + 'ip' => $request->getClientIp(), + 'attributes' => $request->attributes->all(), + )) && parent::matches($request); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php new file mode 100644 index 00000000..41f7a462 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.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\HttpFoundation\File\Exception; + +/** + * Thrown when the access on a file was denied. + * + * @author Bernhard Schussek + */ +class AccessDeniedException extends FileException +{ + /** + * Constructor. + * + * @param string $path The path to the accessed file + */ + public function __construct($path) + { + parent::__construct(sprintf('The file %s could not be accessed', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileException.php b/vendor/symfony/http-foundation/File/Exception/FileException.php new file mode 100644 index 00000000..fad5133e --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred in the component File. + * + * @author Bernhard Schussek + */ +class FileException extends \RuntimeException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php new file mode 100644 index 00000000..ac90d403 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.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\HttpFoundation\File\Exception; + +/** + * Thrown when a file was not found. + * + * @author Bernhard Schussek + */ +class FileNotFoundException extends FileException +{ + /** + * Constructor. + * + * @param string $path The path to the file that was not found + */ + public function __construct($path) + { + parent::__construct(sprintf('The file "%s" does not exist', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 00000000..0444b877 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +class UnexpectedTypeException extends FileException +{ + public function __construct($value, $expectedType) + { + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value))); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/UploadException.php b/vendor/symfony/http-foundation/File/Exception/UploadException.php new file mode 100644 index 00000000..7074e765 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred during file upload. + * + * @author Bernhard Schussek + */ +class UploadException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/File.php b/vendor/symfony/http-foundation/File/File.php new file mode 100644 index 00000000..e2a67684 --- /dev/null +++ b/vendor/symfony/http-foundation/File/File.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; + +/** + * A file in the file system. + * + * @author Bernhard Schussek + */ +class File extends \SplFileInfo +{ + /** + * Constructs a new file from the given path. + * + * @param string $path The path to the file + * @param bool $checkPath Whether to check the path or not + * + * @throws FileNotFoundException If the given path is not a file + */ + public function __construct($path, $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileNotFoundException($path); + } + + parent::__construct($path); + } + + /** + * Returns the extension based on the mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getMimeType() + * to guess the file extension. + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @see ExtensionGuesser + * @see getMimeType() + */ + public function guessExtension() + { + $type = $this->getMimeType(); + $guesser = ExtensionGuesser::getInstance(); + + return $guesser->guess($type); + } + + /** + * Returns the mime type of the file. + * + * The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(), + * mime_content_type() and the system binary "file" (in this order), depending on + * which of those are available. + * + * @return string|null The guessed mime type (e.g. "application/pdf") + * + * @see MimeTypeGuesser + */ + public function getMimeType() + { + $guesser = MimeTypeGuesser::getInstance(); + + return $guesser->guess($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @param string $directory The destination folder + * @param string $name The new file name + * + * @return self A File object representing the new file + * + * @throws FileException if the target file could not be created + */ + public function move($directory, $name = null) + { + $target = $this->getTargetFile($directory, $name); + + if (!@rename($this->getPathname(), $target)) { + $error = error_get_last(); + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + protected function getTargetFile($directory, $name = null) + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); + } + + $target = rtrim($directory, '/\\').DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * Returns locale independent base name of the given path. + * + * @param string $name The new file name + * + * @return string containing + */ + protected function getName($name) + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php new file mode 100644 index 00000000..921751f6 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * A singleton mime type to file extension guesser. + * + * A default guesser is provided. + * You can register custom guessers by calling the register() + * method on the singleton instance: + * + * $guesser = ExtensionGuesser::getInstance(); + * $guesser->register(new MyCustomExtensionGuesser()); + * + * The last registered guesser is preferred over previously registered ones. + */ +class ExtensionGuesser implements ExtensionGuesserInterface +{ + /** + * The singleton instance. + * + * @var ExtensionGuesser + */ + private static $instance = null; + + /** + * All registered ExtensionGuesserInterface instances. + * + * @var array + */ + protected $guessers = array(); + + /** + * Returns the singleton instance. + * + * @return self + */ + public static function getInstance() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Registers all natively provided extension guessers. + */ + private function __construct() + { + $this->register(new MimeTypeExtensionGuesser()); + } + + /** + * Registers a new extension guesser. + * + * When guessing, this guesser is preferred over previously registered ones. + * + * @param ExtensionGuesserInterface $guesser + */ + public function register(ExtensionGuesserInterface $guesser) + { + array_unshift($this->guessers, $guesser); + } + + /** + * Tries to guess the extension. + * + * The mime type is passed to each registered mime type guesser in reverse order + * of their registration (last registered is queried first). Once a guesser + * returns a value that is not NULL, this method terminates and returns the + * value. + * + * @param string $mimeType The mime type + * + * @return string The guessed extension or NULL, if none could be guessed + */ + public function guess($mimeType) + { + foreach ($this->guessers as $guesser) { + if (null !== $extension = $guesser->guess($mimeType)) { + return $extension; + } + } + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php new file mode 100644 index 00000000..d19a0e53 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.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\HttpFoundation\File\MimeType; + +/** + * Guesses the file extension corresponding to a given mime type. + */ +interface ExtensionGuesserInterface +{ + /** + * Makes a best guess for a file extension, given a mime type. + * + * @param string $mimeType The mime type + * + * @return string The guessed extension or NULL, if none could be guessed + */ + public function guess($mimeType); +} diff --git a/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php new file mode 100644 index 00000000..f917a06d --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * Guesses the mime type with the binary "file" (only available on *nix). + * + * @author Bernhard Schussek + */ +class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $cmd; + + /** + * Constructor. + * + * The $cmd pattern must contain a "%s" string that will be replaced + * with the file name to guess. + * + * The command output must start with the mime type of the file. + * + * @param string $cmd The command to run to get the mime type of a file + */ + public function __construct($cmd = 'file -b --mime %s 2>/dev/null') + { + $this->cmd = $cmd; + } + + /** + * Returns whether this guesser is supported on the current OS. + * + * @return bool + */ + public static function isSupported() + { + return '\\' !== DIRECTORY_SEPARATOR && function_exists('passthru') && function_exists('escapeshellarg'); + } + + /** + * {@inheritdoc} + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!self::isSupported()) { + return; + } + + ob_start(); + + // need to use --mime instead of -i. see #6641 + passthru(sprintf($this->cmd, escapeshellarg($path)), $return); + if ($return > 0) { + ob_end_clean(); + + return; + } + + $type = trim(ob_get_clean()); + + if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) { + // it's not a type, but an error message + return; + } + + return $match[1]; + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php new file mode 100644 index 00000000..6fee9479 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * Guesses the mime type using the PECL extension FileInfo. + * + * @author Bernhard Schussek + */ +class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $magicFile; + + /** + * Constructor. + * + * @param string $magicFile A magic file to use with the finfo instance + * + * @see http://www.php.net/manual/en/function.finfo-open.php + */ + public function __construct($magicFile = null) + { + $this->magicFile = $magicFile; + } + + /** + * Returns whether this guesser is supported on the current OS/PHP setup. + * + * @return bool + */ + public static function isSupported() + { + return function_exists('finfo_open'); + } + + /** + * {@inheritdoc} + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!self::isSupported()) { + return; + } + + if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) { + return; + } + + return $finfo->file($path); + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php new file mode 100644 index 00000000..e327f834 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php @@ -0,0 +1,809 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * Provides a best-guess mapping of mime type to file extension. + */ +class MimeTypeExtensionGuesser implements ExtensionGuesserInterface +{ + /** + * A map of mime types and their default extensions. + * + * This list has been placed under the public domain by the Apache HTTPD project. + * This list has been updated from upstream on 2013-04-23. + * + * @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types + * + * @var array + */ + protected $defaultExtensions = array( + 'application/andrew-inset' => 'ez', + 'application/applixware' => 'aw', + 'application/atom+xml' => 'atom', + 'application/atomcat+xml' => 'atomcat', + 'application/atomsvc+xml' => 'atomsvc', + 'application/ccxml+xml' => 'ccxml', + 'application/cdmi-capability' => 'cdmia', + 'application/cdmi-container' => 'cdmic', + 'application/cdmi-domain' => 'cdmid', + 'application/cdmi-object' => 'cdmio', + 'application/cdmi-queue' => 'cdmiq', + 'application/cu-seeme' => 'cu', + 'application/davmount+xml' => 'davmount', + 'application/docbook+xml' => 'dbk', + 'application/dssc+der' => 'dssc', + 'application/dssc+xml' => 'xdssc', + 'application/ecmascript' => 'ecma', + 'application/emma+xml' => 'emma', + 'application/epub+zip' => 'epub', + 'application/exi' => 'exi', + 'application/font-tdpfr' => 'pfr', + 'application/gml+xml' => 'gml', + 'application/gpx+xml' => 'gpx', + 'application/gxf' => 'gxf', + 'application/hyperstudio' => 'stk', + 'application/inkml+xml' => 'ink', + 'application/ipfix' => 'ipfix', + 'application/java-archive' => 'jar', + 'application/java-serialized-object' => 'ser', + 'application/java-vm' => 'class', + 'application/javascript' => 'js', + 'application/json' => 'json', + 'application/jsonml+json' => 'jsonml', + 'application/lost+xml' => 'lostxml', + 'application/mac-binhex40' => 'hqx', + 'application/mac-compactpro' => 'cpt', + 'application/mads+xml' => 'mads', + 'application/marc' => 'mrc', + 'application/marcxml+xml' => 'mrcx', + 'application/mathematica' => 'ma', + 'application/mathml+xml' => 'mathml', + 'application/mbox' => 'mbox', + 'application/mediaservercontrol+xml' => 'mscml', + 'application/metalink+xml' => 'metalink', + 'application/metalink4+xml' => 'meta4', + 'application/mets+xml' => 'mets', + 'application/mods+xml' => 'mods', + 'application/mp21' => 'm21', + 'application/mp4' => 'mp4s', + 'application/msword' => 'doc', + 'application/mxf' => 'mxf', + 'application/octet-stream' => 'bin', + 'application/oda' => 'oda', + 'application/oebps-package+xml' => 'opf', + 'application/ogg' => 'ogx', + 'application/omdoc+xml' => 'omdoc', + 'application/onenote' => 'onetoc', + 'application/oxps' => 'oxps', + 'application/patch-ops-error+xml' => 'xer', + 'application/pdf' => 'pdf', + 'application/pgp-encrypted' => 'pgp', + 'application/pgp-signature' => 'asc', + 'application/pics-rules' => 'prf', + 'application/pkcs10' => 'p10', + 'application/pkcs7-mime' => 'p7m', + 'application/pkcs7-signature' => 'p7s', + 'application/pkcs8' => 'p8', + 'application/pkix-attr-cert' => 'ac', + 'application/pkix-cert' => 'cer', + 'application/pkix-crl' => 'crl', + 'application/pkix-pkipath' => 'pkipath', + 'application/pkixcmp' => 'pki', + 'application/pls+xml' => 'pls', + 'application/postscript' => 'ai', + 'application/prs.cww' => 'cww', + 'application/pskc+xml' => 'pskcxml', + 'application/rdf+xml' => 'rdf', + 'application/reginfo+xml' => 'rif', + 'application/relax-ng-compact-syntax' => 'rnc', + 'application/resource-lists+xml' => 'rl', + 'application/resource-lists-diff+xml' => 'rld', + 'application/rls-services+xml' => 'rs', + 'application/rpki-ghostbusters' => 'gbr', + 'application/rpki-manifest' => 'mft', + 'application/rpki-roa' => 'roa', + 'application/rsd+xml' => 'rsd', + 'application/rss+xml' => 'rss', + 'application/rtf' => 'rtf', + 'application/sbml+xml' => 'sbml', + 'application/scvp-cv-request' => 'scq', + 'application/scvp-cv-response' => 'scs', + 'application/scvp-vp-request' => 'spq', + 'application/scvp-vp-response' => 'spp', + 'application/sdp' => 'sdp', + 'application/set-payment-initiation' => 'setpay', + 'application/set-registration-initiation' => 'setreg', + 'application/shf+xml' => 'shf', + 'application/smil+xml' => 'smi', + 'application/sparql-query' => 'rq', + 'application/sparql-results+xml' => 'srx', + 'application/srgs' => 'gram', + 'application/srgs+xml' => 'grxml', + 'application/sru+xml' => 'sru', + 'application/ssdl+xml' => 'ssdl', + 'application/ssml+xml' => 'ssml', + 'application/tei+xml' => 'tei', + 'application/thraud+xml' => 'tfi', + 'application/timestamped-data' => 'tsd', + 'application/vnd.3gpp.pic-bw-large' => 'plb', + 'application/vnd.3gpp.pic-bw-small' => 'psb', + 'application/vnd.3gpp.pic-bw-var' => 'pvb', + 'application/vnd.3gpp2.tcap' => 'tcap', + 'application/vnd.3m.post-it-notes' => 'pwn', + 'application/vnd.accpac.simply.aso' => 'aso', + 'application/vnd.accpac.simply.imp' => 'imp', + 'application/vnd.acucobol' => 'acu', + 'application/vnd.acucorp' => 'atc', + 'application/vnd.adobe.air-application-installer-package+zip' => 'air', + 'application/vnd.adobe.formscentral.fcdt' => 'fcdt', + 'application/vnd.adobe.fxp' => 'fxp', + 'application/vnd.adobe.xdp+xml' => 'xdp', + 'application/vnd.adobe.xfdf' => 'xfdf', + 'application/vnd.ahead.space' => 'ahead', + 'application/vnd.airzip.filesecure.azf' => 'azf', + 'application/vnd.airzip.filesecure.azs' => 'azs', + 'application/vnd.amazon.ebook' => 'azw', + 'application/vnd.americandynamics.acc' => 'acc', + 'application/vnd.amiga.ami' => 'ami', + 'application/vnd.android.package-archive' => 'apk', + 'application/vnd.anser-web-certificate-issue-initiation' => 'cii', + 'application/vnd.anser-web-funds-transfer-initiation' => 'fti', + 'application/vnd.antix.game-component' => 'atx', + 'application/vnd.apple.installer+xml' => 'mpkg', + 'application/vnd.apple.mpegurl' => 'm3u8', + 'application/vnd.aristanetworks.swi' => 'swi', + 'application/vnd.astraea-software.iota' => 'iota', + 'application/vnd.audiograph' => 'aep', + 'application/vnd.blueice.multipass' => 'mpm', + 'application/vnd.bmi' => 'bmi', + 'application/vnd.businessobjects' => 'rep', + 'application/vnd.chemdraw+xml' => 'cdxml', + 'application/vnd.chipnuts.karaoke-mmd' => 'mmd', + 'application/vnd.cinderella' => 'cdy', + 'application/vnd.claymore' => 'cla', + 'application/vnd.cloanto.rp9' => 'rp9', + 'application/vnd.clonk.c4group' => 'c4g', + 'application/vnd.cluetrust.cartomobile-config' => 'c11amc', + 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz', + 'application/vnd.commonspace' => 'csp', + 'application/vnd.contact.cmsg' => 'cdbcmsg', + 'application/vnd.cosmocaller' => 'cmc', + 'application/vnd.crick.clicker' => 'clkx', + 'application/vnd.crick.clicker.keyboard' => 'clkk', + 'application/vnd.crick.clicker.palette' => 'clkp', + 'application/vnd.crick.clicker.template' => 'clkt', + 'application/vnd.crick.clicker.wordbank' => 'clkw', + 'application/vnd.criticaltools.wbs+xml' => 'wbs', + 'application/vnd.ctc-posml' => 'pml', + 'application/vnd.cups-ppd' => 'ppd', + 'application/vnd.curl.car' => 'car', + 'application/vnd.curl.pcurl' => 'pcurl', + 'application/vnd.dart' => 'dart', + 'application/vnd.data-vision.rdz' => 'rdz', + 'application/vnd.dece.data' => 'uvf', + 'application/vnd.dece.ttml+xml' => 'uvt', + 'application/vnd.dece.unspecified' => 'uvx', + 'application/vnd.dece.zip' => 'uvz', + 'application/vnd.denovo.fcselayout-link' => 'fe_launch', + 'application/vnd.dna' => 'dna', + 'application/vnd.dolby.mlp' => 'mlp', + 'application/vnd.dpgraph' => 'dpg', + 'application/vnd.dreamfactory' => 'dfac', + 'application/vnd.ds-keypoint' => 'kpxx', + 'application/vnd.dvb.ait' => 'ait', + 'application/vnd.dvb.service' => 'svc', + 'application/vnd.dynageo' => 'geo', + 'application/vnd.ecowin.chart' => 'mag', + 'application/vnd.enliven' => 'nml', + 'application/vnd.epson.esf' => 'esf', + 'application/vnd.epson.msf' => 'msf', + 'application/vnd.epson.quickanime' => 'qam', + 'application/vnd.epson.salt' => 'slt', + 'application/vnd.epson.ssf' => 'ssf', + 'application/vnd.eszigno3+xml' => 'es3', + 'application/vnd.ezpix-album' => 'ez2', + 'application/vnd.ezpix-package' => 'ez3', + 'application/vnd.fdf' => 'fdf', + 'application/vnd.fdsn.mseed' => 'mseed', + 'application/vnd.fdsn.seed' => 'seed', + 'application/vnd.flographit' => 'gph', + 'application/vnd.fluxtime.clip' => 'ftc', + 'application/vnd.framemaker' => 'fm', + 'application/vnd.frogans.fnc' => 'fnc', + 'application/vnd.frogans.ltf' => 'ltf', + 'application/vnd.fsc.weblaunch' => 'fsc', + 'application/vnd.fujitsu.oasys' => 'oas', + 'application/vnd.fujitsu.oasys2' => 'oa2', + 'application/vnd.fujitsu.oasys3' => 'oa3', + 'application/vnd.fujitsu.oasysgp' => 'fg5', + 'application/vnd.fujitsu.oasysprs' => 'bh2', + 'application/vnd.fujixerox.ddd' => 'ddd', + 'application/vnd.fujixerox.docuworks' => 'xdw', + 'application/vnd.fujixerox.docuworks.binder' => 'xbd', + 'application/vnd.fuzzysheet' => 'fzs', + 'application/vnd.genomatix.tuxedo' => 'txd', + 'application/vnd.geogebra.file' => 'ggb', + 'application/vnd.geogebra.tool' => 'ggt', + 'application/vnd.geometry-explorer' => 'gex', + 'application/vnd.geonext' => 'gxt', + 'application/vnd.geoplan' => 'g2w', + 'application/vnd.geospace' => 'g3w', + 'application/vnd.gmx' => 'gmx', + 'application/vnd.google-earth.kml+xml' => 'kml', + 'application/vnd.google-earth.kmz' => 'kmz', + 'application/vnd.grafeq' => 'gqf', + 'application/vnd.groove-account' => 'gac', + 'application/vnd.groove-help' => 'ghf', + 'application/vnd.groove-identity-message' => 'gim', + 'application/vnd.groove-injector' => 'grv', + 'application/vnd.groove-tool-message' => 'gtm', + 'application/vnd.groove-tool-template' => 'tpl', + 'application/vnd.groove-vcard' => 'vcg', + 'application/vnd.hal+xml' => 'hal', + 'application/vnd.handheld-entertainment+xml' => 'zmm', + 'application/vnd.hbci' => 'hbci', + 'application/vnd.hhe.lesson-player' => 'les', + 'application/vnd.hp-hpgl' => 'hpgl', + 'application/vnd.hp-hpid' => 'hpid', + 'application/vnd.hp-hps' => 'hps', + 'application/vnd.hp-jlyt' => 'jlt', + 'application/vnd.hp-pcl' => 'pcl', + 'application/vnd.hp-pclxl' => 'pclxl', + 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx', + 'application/vnd.ibm.minipay' => 'mpy', + 'application/vnd.ibm.modcap' => 'afp', + 'application/vnd.ibm.rights-management' => 'irm', + 'application/vnd.ibm.secure-container' => 'sc', + 'application/vnd.iccprofile' => 'icc', + 'application/vnd.igloader' => 'igl', + 'application/vnd.immervision-ivp' => 'ivp', + 'application/vnd.immervision-ivu' => 'ivu', + 'application/vnd.insors.igm' => 'igm', + 'application/vnd.intercon.formnet' => 'xpw', + 'application/vnd.intergeo' => 'i2g', + 'application/vnd.intu.qbo' => 'qbo', + 'application/vnd.intu.qfx' => 'qfx', + 'application/vnd.ipunplugged.rcprofile' => 'rcprofile', + 'application/vnd.irepository.package+xml' => 'irp', + 'application/vnd.is-xpr' => 'xpr', + 'application/vnd.isac.fcs' => 'fcs', + 'application/vnd.jam' => 'jam', + 'application/vnd.jcp.javame.midlet-rms' => 'rms', + 'application/vnd.jisp' => 'jisp', + 'application/vnd.joost.joda-archive' => 'joda', + 'application/vnd.kahootz' => 'ktz', + 'application/vnd.kde.karbon' => 'karbon', + 'application/vnd.kde.kchart' => 'chrt', + 'application/vnd.kde.kformula' => 'kfo', + 'application/vnd.kde.kivio' => 'flw', + 'application/vnd.kde.kontour' => 'kon', + 'application/vnd.kde.kpresenter' => 'kpr', + 'application/vnd.kde.kspread' => 'ksp', + 'application/vnd.kde.kword' => 'kwd', + 'application/vnd.kenameaapp' => 'htke', + 'application/vnd.kidspiration' => 'kia', + 'application/vnd.kinar' => 'kne', + 'application/vnd.koan' => 'skp', + 'application/vnd.kodak-descriptor' => 'sse', + 'application/vnd.las.las+xml' => 'lasxml', + 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', + 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', + 'application/vnd.lotus-1-2-3' => '123', + 'application/vnd.lotus-approach' => 'apr', + 'application/vnd.lotus-freelance' => 'pre', + 'application/vnd.lotus-notes' => 'nsf', + 'application/vnd.lotus-organizer' => 'org', + 'application/vnd.lotus-screencam' => 'scm', + 'application/vnd.lotus-wordpro' => 'lwp', + 'application/vnd.macports.portpkg' => 'portpkg', + 'application/vnd.mcd' => 'mcd', + 'application/vnd.medcalcdata' => 'mc1', + 'application/vnd.mediastation.cdkey' => 'cdkey', + 'application/vnd.mfer' => 'mwf', + 'application/vnd.mfmp' => 'mfm', + 'application/vnd.micrografx.flo' => 'flo', + 'application/vnd.micrografx.igx' => 'igx', + 'application/vnd.mif' => 'mif', + 'application/vnd.mobius.daf' => 'daf', + 'application/vnd.mobius.dis' => 'dis', + 'application/vnd.mobius.mbk' => 'mbk', + 'application/vnd.mobius.mqy' => 'mqy', + 'application/vnd.mobius.msl' => 'msl', + 'application/vnd.mobius.plc' => 'plc', + 'application/vnd.mobius.txf' => 'txf', + 'application/vnd.mophun.application' => 'mpn', + 'application/vnd.mophun.certificate' => 'mpc', + 'application/vnd.mozilla.xul+xml' => 'xul', + 'application/vnd.ms-artgalry' => 'cil', + 'application/vnd.ms-cab-compressed' => 'cab', + 'application/vnd.ms-excel' => 'xls', + 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam', + 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb', + 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', + 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm', + 'application/vnd.ms-fontobject' => 'eot', + 'application/vnd.ms-htmlhelp' => 'chm', + 'application/vnd.ms-ims' => 'ims', + 'application/vnd.ms-lrm' => 'lrm', + 'application/vnd.ms-officetheme' => 'thmx', + 'application/vnd.ms-pki.seccat' => 'cat', + 'application/vnd.ms-pki.stl' => 'stl', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam', + 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm', + 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm', + 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm', + 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm', + 'application/vnd.ms-project' => 'mpp', + 'application/vnd.ms-word.document.macroenabled.12' => 'docm', + 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', + 'application/vnd.ms-works' => 'wps', + 'application/vnd.ms-wpl' => 'wpl', + 'application/vnd.ms-xpsdocument' => 'xps', + 'application/vnd.mseq' => 'mseq', + 'application/vnd.musician' => 'mus', + 'application/vnd.muvee.style' => 'msty', + 'application/vnd.mynfc' => 'taglet', + 'application/vnd.neurolanguage.nlu' => 'nlu', + 'application/vnd.nitf' => 'ntf', + 'application/vnd.noblenet-directory' => 'nnd', + 'application/vnd.noblenet-sealer' => 'nns', + 'application/vnd.noblenet-web' => 'nnw', + 'application/vnd.nokia.n-gage.data' => 'ngdat', + 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage', + 'application/vnd.nokia.radio-preset' => 'rpst', + 'application/vnd.nokia.radio-presets' => 'rpss', + 'application/vnd.novadigm.edm' => 'edm', + 'application/vnd.novadigm.edx' => 'edx', + 'application/vnd.novadigm.ext' => 'ext', + 'application/vnd.oasis.opendocument.chart' => 'odc', + 'application/vnd.oasis.opendocument.chart-template' => 'otc', + 'application/vnd.oasis.opendocument.database' => 'odb', + 'application/vnd.oasis.opendocument.formula' => 'odf', + 'application/vnd.oasis.opendocument.formula-template' => 'odft', + 'application/vnd.oasis.opendocument.graphics' => 'odg', + 'application/vnd.oasis.opendocument.graphics-template' => 'otg', + 'application/vnd.oasis.opendocument.image' => 'odi', + 'application/vnd.oasis.opendocument.image-template' => 'oti', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/vnd.oasis.opendocument.presentation-template' => 'otp', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.text-master' => 'odm', + 'application/vnd.oasis.opendocument.text-template' => 'ott', + 'application/vnd.oasis.opendocument.text-web' => 'oth', + 'application/vnd.olpc-sugar' => 'xo', + 'application/vnd.oma.dd2+xml' => 'dd2', + 'application/vnd.openofficeorg.extension' => 'oxt', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', + 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', + 'application/vnd.osgeo.mapguide.package' => 'mgp', + 'application/vnd.osgi.dp' => 'dp', + 'application/vnd.osgi.subsystem' => 'esa', + 'application/vnd.palm' => 'pdb', + 'application/vnd.pawaafile' => 'paw', + 'application/vnd.pg.format' => 'str', + 'application/vnd.pg.osasli' => 'ei6', + 'application/vnd.picsel' => 'efif', + 'application/vnd.pmi.widget' => 'wg', + 'application/vnd.pocketlearn' => 'plf', + 'application/vnd.powerbuilder6' => 'pbd', + 'application/vnd.previewsystems.box' => 'box', + 'application/vnd.proteus.magazine' => 'mgz', + 'application/vnd.publishare-delta-tree' => 'qps', + 'application/vnd.pvi.ptid1' => 'ptid', + 'application/vnd.quark.quarkxpress' => 'qxd', + 'application/vnd.realvnc.bed' => 'bed', + 'application/vnd.recordare.musicxml' => 'mxl', + 'application/vnd.recordare.musicxml+xml' => 'musicxml', + 'application/vnd.rig.cryptonote' => 'cryptonote', + 'application/vnd.rim.cod' => 'cod', + 'application/vnd.rn-realmedia' => 'rm', + 'application/vnd.rn-realmedia-vbr' => 'rmvb', + 'application/vnd.route66.link66+xml' => 'link66', + 'application/vnd.sailingtracker.track' => 'st', + 'application/vnd.seemail' => 'see', + 'application/vnd.sema' => 'sema', + 'application/vnd.semd' => 'semd', + 'application/vnd.semf' => 'semf', + 'application/vnd.shana.informed.formdata' => 'ifm', + 'application/vnd.shana.informed.formtemplate' => 'itp', + 'application/vnd.shana.informed.interchange' => 'iif', + 'application/vnd.shana.informed.package' => 'ipk', + 'application/vnd.simtech-mindmapper' => 'twd', + 'application/vnd.smaf' => 'mmf', + 'application/vnd.smart.teacher' => 'teacher', + 'application/vnd.solent.sdkm+xml' => 'sdkm', + 'application/vnd.spotfire.dxp' => 'dxp', + 'application/vnd.spotfire.sfs' => 'sfs', + 'application/vnd.stardivision.calc' => 'sdc', + 'application/vnd.stardivision.draw' => 'sda', + 'application/vnd.stardivision.impress' => 'sdd', + 'application/vnd.stardivision.math' => 'smf', + 'application/vnd.stardivision.writer' => 'sdw', + 'application/vnd.stardivision.writer-global' => 'sgl', + 'application/vnd.stepmania.package' => 'smzip', + 'application/vnd.stepmania.stepchart' => 'sm', + 'application/vnd.sun.xml.calc' => 'sxc', + 'application/vnd.sun.xml.calc.template' => 'stc', + 'application/vnd.sun.xml.draw' => 'sxd', + 'application/vnd.sun.xml.draw.template' => 'std', + 'application/vnd.sun.xml.impress' => 'sxi', + 'application/vnd.sun.xml.impress.template' => 'sti', + 'application/vnd.sun.xml.math' => 'sxm', + 'application/vnd.sun.xml.writer' => 'sxw', + 'application/vnd.sun.xml.writer.global' => 'sxg', + 'application/vnd.sun.xml.writer.template' => 'stw', + 'application/vnd.sus-calendar' => 'sus', + 'application/vnd.svd' => 'svd', + 'application/vnd.symbian.install' => 'sis', + 'application/vnd.syncml+xml' => 'xsm', + 'application/vnd.syncml.dm+wbxml' => 'bdm', + 'application/vnd.syncml.dm+xml' => 'xdm', + 'application/vnd.tao.intent-module-archive' => 'tao', + 'application/vnd.tcpdump.pcap' => 'pcap', + 'application/vnd.tmobile-livetv' => 'tmo', + 'application/vnd.trid.tpt' => 'tpt', + 'application/vnd.triscape.mxs' => 'mxs', + 'application/vnd.trueapp' => 'tra', + 'application/vnd.ufdl' => 'ufd', + 'application/vnd.uiq.theme' => 'utz', + 'application/vnd.umajin' => 'umj', + 'application/vnd.unity' => 'unityweb', + 'application/vnd.uoml+xml' => 'uoml', + 'application/vnd.vcx' => 'vcx', + 'application/vnd.visio' => 'vsd', + 'application/vnd.visionary' => 'vis', + 'application/vnd.vsf' => 'vsf', + 'application/vnd.wap.wbxml' => 'wbxml', + 'application/vnd.wap.wmlc' => 'wmlc', + 'application/vnd.wap.wmlscriptc' => 'wmlsc', + 'application/vnd.webturbo' => 'wtb', + 'application/vnd.wolfram.player' => 'nbp', + 'application/vnd.wordperfect' => 'wpd', + 'application/vnd.wqd' => 'wqd', + 'application/vnd.wt.stf' => 'stf', + 'application/vnd.xara' => 'xar', + 'application/vnd.xfdl' => 'xfdl', + 'application/vnd.yamaha.hv-dic' => 'hvd', + 'application/vnd.yamaha.hv-script' => 'hvs', + 'application/vnd.yamaha.hv-voice' => 'hvp', + 'application/vnd.yamaha.openscoreformat' => 'osf', + 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg', + 'application/vnd.yamaha.smaf-audio' => 'saf', + 'application/vnd.yamaha.smaf-phrase' => 'spf', + 'application/vnd.yellowriver-custom-menu' => 'cmp', + 'application/vnd.zul' => 'zir', + 'application/vnd.zzazz.deck+xml' => 'zaz', + 'application/voicexml+xml' => 'vxml', + 'application/widget' => 'wgt', + 'application/winhlp' => 'hlp', + 'application/wsdl+xml' => 'wsdl', + 'application/wspolicy+xml' => 'wspolicy', + 'application/x-7z-compressed' => '7z', + 'application/x-abiword' => 'abw', + 'application/x-ace-compressed' => 'ace', + 'application/x-apple-diskimage' => 'dmg', + 'application/x-authorware-bin' => 'aab', + 'application/x-authorware-map' => 'aam', + 'application/x-authorware-seg' => 'aas', + 'application/x-bcpio' => 'bcpio', + 'application/x-bittorrent' => 'torrent', + 'application/x-blorb' => 'blb', + 'application/x-bzip' => 'bz', + 'application/x-bzip2' => 'bz2', + 'application/x-cbr' => 'cbr', + 'application/x-cdlink' => 'vcd', + 'application/x-cfs-compressed' => 'cfs', + 'application/x-chat' => 'chat', + 'application/x-chess-pgn' => 'pgn', + 'application/x-conference' => 'nsc', + 'application/x-cpio' => 'cpio', + 'application/x-csh' => 'csh', + 'application/x-debian-package' => 'deb', + 'application/x-dgc-compressed' => 'dgc', + 'application/x-director' => 'dir', + 'application/x-doom' => 'wad', + 'application/x-dtbncx+xml' => 'ncx', + 'application/x-dtbook+xml' => 'dtb', + 'application/x-dtbresource+xml' => 'res', + 'application/x-dvi' => 'dvi', + 'application/x-envoy' => 'evy', + 'application/x-eva' => 'eva', + 'application/x-font-bdf' => 'bdf', + 'application/x-font-ghostscript' => 'gsf', + 'application/x-font-linux-psf' => 'psf', + 'application/x-font-otf' => 'otf', + 'application/x-font-pcf' => 'pcf', + 'application/x-font-snf' => 'snf', + 'application/x-font-ttf' => 'ttf', + 'application/x-font-type1' => 'pfa', + 'application/x-font-woff' => 'woff', + 'application/x-freearc' => 'arc', + 'application/x-futuresplash' => 'spl', + 'application/x-gca-compressed' => 'gca', + 'application/x-glulx' => 'ulx', + 'application/x-gnumeric' => 'gnumeric', + 'application/x-gramps-xml' => 'gramps', + 'application/x-gtar' => 'gtar', + 'application/x-hdf' => 'hdf', + 'application/x-install-instructions' => 'install', + 'application/x-iso9660-image' => 'iso', + 'application/x-java-jnlp-file' => 'jnlp', + 'application/x-latex' => 'latex', + 'application/x-lzh-compressed' => 'lzh', + 'application/x-mie' => 'mie', + 'application/x-mobipocket-ebook' => 'prc', + 'application/x-ms-application' => 'application', + 'application/x-ms-shortcut' => 'lnk', + 'application/x-ms-wmd' => 'wmd', + 'application/x-ms-wmz' => 'wmz', + 'application/x-ms-xbap' => 'xbap', + 'application/x-msaccess' => 'mdb', + 'application/x-msbinder' => 'obd', + 'application/x-mscardfile' => 'crd', + 'application/x-msclip' => 'clp', + 'application/x-msdownload' => 'exe', + 'application/x-msmediaview' => 'mvb', + 'application/x-msmetafile' => 'wmf', + 'application/x-msmoney' => 'mny', + 'application/x-mspublisher' => 'pub', + 'application/x-msschedule' => 'scd', + 'application/x-msterminal' => 'trm', + 'application/x-mswrite' => 'wri', + 'application/x-netcdf' => 'nc', + 'application/x-nzb' => 'nzb', + 'application/x-pkcs12' => 'p12', + 'application/x-pkcs7-certificates' => 'p7b', + 'application/x-pkcs7-certreqresp' => 'p7r', + 'application/x-rar-compressed' => 'rar', + 'application/x-rar' => 'rar', + 'application/x-research-info-systems' => 'ris', + 'application/x-sh' => 'sh', + 'application/x-shar' => 'shar', + 'application/x-shockwave-flash' => 'swf', + 'application/x-silverlight-app' => 'xap', + 'application/x-sql' => 'sql', + 'application/x-stuffit' => 'sit', + 'application/x-stuffitx' => 'sitx', + 'application/x-subrip' => 'srt', + 'application/x-sv4cpio' => 'sv4cpio', + 'application/x-sv4crc' => 'sv4crc', + 'application/x-t3vm-image' => 't3', + 'application/x-tads' => 'gam', + 'application/x-tar' => 'tar', + 'application/x-tcl' => 'tcl', + 'application/x-tex' => 'tex', + 'application/x-tex-tfm' => 'tfm', + 'application/x-texinfo' => 'texinfo', + 'application/x-tgif' => 'obj', + 'application/x-ustar' => 'ustar', + 'application/x-wais-source' => 'src', + 'application/x-x509-ca-cert' => 'der', + 'application/x-xfig' => 'fig', + 'application/x-xliff+xml' => 'xlf', + 'application/x-xpinstall' => 'xpi', + 'application/x-xz' => 'xz', + 'application/x-zmachine' => 'z1', + 'application/xaml+xml' => 'xaml', + 'application/xcap-diff+xml' => 'xdf', + 'application/xenc+xml' => 'xenc', + 'application/xhtml+xml' => 'xhtml', + 'application/xml' => 'xml', + 'application/xml-dtd' => 'dtd', + 'application/xop+xml' => 'xop', + 'application/xproc+xml' => 'xpl', + 'application/xslt+xml' => 'xslt', + 'application/xspf+xml' => 'xspf', + 'application/xv+xml' => 'mxml', + 'application/yang' => 'yang', + 'application/yin+xml' => 'yin', + 'application/zip' => 'zip', + 'audio/adpcm' => 'adp', + 'audio/basic' => 'au', + 'audio/midi' => 'mid', + 'audio/mp4' => 'mp4a', + 'audio/mpeg' => 'mpga', + 'audio/ogg' => 'oga', + 'audio/s3m' => 's3m', + 'audio/silk' => 'sil', + 'audio/vnd.dece.audio' => 'uva', + 'audio/vnd.digital-winds' => 'eol', + 'audio/vnd.dra' => 'dra', + 'audio/vnd.dts' => 'dts', + 'audio/vnd.dts.hd' => 'dtshd', + 'audio/vnd.lucent.voice' => 'lvp', + 'audio/vnd.ms-playready.media.pya' => 'pya', + 'audio/vnd.nuera.ecelp4800' => 'ecelp4800', + 'audio/vnd.nuera.ecelp7470' => 'ecelp7470', + 'audio/vnd.nuera.ecelp9600' => 'ecelp9600', + 'audio/vnd.rip' => 'rip', + 'audio/webm' => 'weba', + 'audio/x-aac' => 'aac', + 'audio/x-aiff' => 'aif', + 'audio/x-caf' => 'caf', + 'audio/x-flac' => 'flac', + 'audio/x-matroska' => 'mka', + 'audio/x-mpegurl' => 'm3u', + 'audio/x-ms-wax' => 'wax', + 'audio/x-ms-wma' => 'wma', + 'audio/x-pn-realaudio' => 'ram', + 'audio/x-pn-realaudio-plugin' => 'rmp', + 'audio/x-wav' => 'wav', + 'audio/xm' => 'xm', + 'chemical/x-cdx' => 'cdx', + 'chemical/x-cif' => 'cif', + 'chemical/x-cmdf' => 'cmdf', + 'chemical/x-cml' => 'cml', + 'chemical/x-csml' => 'csml', + 'chemical/x-xyz' => 'xyz', + 'image/bmp' => 'bmp', + 'image/x-ms-bmp' => 'bmp', + 'image/cgm' => 'cgm', + 'image/g3fax' => 'g3', + 'image/gif' => 'gif', + 'image/ief' => 'ief', + 'image/jpeg' => 'jpeg', + 'image/pjpeg' => 'jpeg', + 'image/ktx' => 'ktx', + 'image/png' => 'png', + 'image/prs.btif' => 'btif', + 'image/sgi' => 'sgi', + 'image/svg+xml' => 'svg', + 'image/tiff' => 'tiff', + 'image/vnd.adobe.photoshop' => 'psd', + 'image/vnd.dece.graphic' => 'uvi', + 'image/vnd.dvb.subtitle' => 'sub', + 'image/vnd.djvu' => 'djvu', + 'image/vnd.dwg' => 'dwg', + 'image/vnd.dxf' => 'dxf', + 'image/vnd.fastbidsheet' => 'fbs', + 'image/vnd.fpx' => 'fpx', + 'image/vnd.fst' => 'fst', + 'image/vnd.fujixerox.edmics-mmr' => 'mmr', + 'image/vnd.fujixerox.edmics-rlc' => 'rlc', + 'image/vnd.ms-modi' => 'mdi', + 'image/vnd.ms-photo' => 'wdp', + 'image/vnd.net-fpx' => 'npx', + 'image/vnd.wap.wbmp' => 'wbmp', + 'image/vnd.xiff' => 'xif', + 'image/webp' => 'webp', + 'image/x-3ds' => '3ds', + 'image/x-cmu-raster' => 'ras', + 'image/x-cmx' => 'cmx', + 'image/x-freehand' => 'fh', + 'image/x-icon' => 'ico', + 'image/x-mrsid-image' => 'sid', + 'image/x-pcx' => 'pcx', + 'image/x-pict' => 'pic', + 'image/x-portable-anymap' => 'pnm', + 'image/x-portable-bitmap' => 'pbm', + 'image/x-portable-graymap' => 'pgm', + 'image/x-portable-pixmap' => 'ppm', + 'image/x-rgb' => 'rgb', + 'image/x-tga' => 'tga', + 'image/x-xbitmap' => 'xbm', + 'image/x-xpixmap' => 'xpm', + 'image/x-xwindowdump' => 'xwd', + 'message/rfc822' => 'eml', + 'model/iges' => 'igs', + 'model/mesh' => 'msh', + 'model/vnd.collada+xml' => 'dae', + 'model/vnd.dwf' => 'dwf', + 'model/vnd.gdl' => 'gdl', + 'model/vnd.gtw' => 'gtw', + 'model/vnd.mts' => 'mts', + 'model/vnd.vtu' => 'vtu', + 'model/vrml' => 'wrl', + 'model/x3d+binary' => 'x3db', + 'model/x3d+vrml' => 'x3dv', + 'model/x3d+xml' => 'x3d', + 'text/cache-manifest' => 'appcache', + 'text/calendar' => 'ics', + 'text/css' => 'css', + 'text/csv' => 'csv', + 'text/html' => 'html', + 'text/n3' => 'n3', + 'text/plain' => 'txt', + 'text/prs.lines.tag' => 'dsc', + 'text/richtext' => 'rtx', + 'text/rtf' => 'rtf', + 'text/sgml' => 'sgml', + 'text/tab-separated-values' => 'tsv', + 'text/troff' => 't', + 'text/turtle' => 'ttl', + 'text/uri-list' => 'uri', + 'text/vcard' => 'vcard', + 'text/vnd.curl' => 'curl', + 'text/vnd.curl.dcurl' => 'dcurl', + 'text/vnd.curl.scurl' => 'scurl', + 'text/vnd.curl.mcurl' => 'mcurl', + 'text/vnd.dvb.subtitle' => 'sub', + 'text/vnd.fly' => 'fly', + 'text/vnd.fmi.flexstor' => 'flx', + 'text/vnd.graphviz' => 'gv', + 'text/vnd.in3d.3dml' => '3dml', + 'text/vnd.in3d.spot' => 'spot', + 'text/vnd.sun.j2me.app-descriptor' => 'jad', + 'text/vnd.wap.wml' => 'wml', + 'text/vnd.wap.wmlscript' => 'wmls', + 'text/vtt' => 'vtt', + 'text/x-asm' => 's', + 'text/x-c' => 'c', + 'text/x-fortran' => 'f', + 'text/x-pascal' => 'p', + 'text/x-java-source' => 'java', + 'text/x-opml' => 'opml', + 'text/x-nfo' => 'nfo', + 'text/x-setext' => 'etx', + 'text/x-sfv' => 'sfv', + 'text/x-uuencode' => 'uu', + 'text/x-vcalendar' => 'vcs', + 'text/x-vcard' => 'vcf', + 'video/3gpp' => '3gp', + 'video/3gpp2' => '3g2', + 'video/h261' => 'h261', + 'video/h263' => 'h263', + 'video/h264' => 'h264', + 'video/jpeg' => 'jpgv', + 'video/jpm' => 'jpm', + 'video/mj2' => 'mj2', + 'video/mp4' => 'mp4', + 'video/mpeg' => 'mpeg', + 'video/ogg' => 'ogv', + 'video/quicktime' => 'qt', + 'video/vnd.dece.hd' => 'uvh', + 'video/vnd.dece.mobile' => 'uvm', + 'video/vnd.dece.pd' => 'uvp', + 'video/vnd.dece.sd' => 'uvs', + 'video/vnd.dece.video' => 'uvv', + 'video/vnd.dvb.file' => 'dvb', + 'video/vnd.fvt' => 'fvt', + 'video/vnd.mpegurl' => 'mxu', + 'video/vnd.ms-playready.media.pyv' => 'pyv', + 'video/vnd.uvvu.mp4' => 'uvu', + 'video/vnd.vivo' => 'viv', + 'video/webm' => 'webm', + 'video/x-f4v' => 'f4v', + 'video/x-fli' => 'fli', + 'video/x-flv' => 'flv', + 'video/x-m4v' => 'm4v', + 'video/x-matroska' => 'mkv', + 'video/x-mng' => 'mng', + 'video/x-ms-asf' => 'asf', + 'video/x-ms-vob' => 'vob', + 'video/x-ms-wm' => 'wm', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-wmx' => 'wmx', + 'video/x-ms-wvx' => 'wvx', + 'video/x-msvideo' => 'avi', + 'video/x-sgi-movie' => 'movie', + 'video/x-smv' => 'smv', + 'x-conference/x-cooltalk' => 'ice', + ); + + /** + * {@inheritdoc} + */ + public function guess($mimeType) + { + return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null; + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php new file mode 100644 index 00000000..69c803b4 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * A singleton mime type guesser. + * + * By default, all mime type guessers provided by the framework are installed + * (if available on the current OS/PHP setup). + * + * You can register custom guessers by calling the register() method on the + * singleton instance. Custom guessers are always called before any default ones. + * + * $guesser = MimeTypeGuesser::getInstance(); + * $guesser->register(new MyCustomMimeTypeGuesser()); + * + * If you want to change the order of the default guessers, just re-register your + * preferred one as a custom one. The last registered guesser is preferred over + * previously registered ones. + * + * Re-registering a built-in guesser also allows you to configure it: + * + * $guesser = MimeTypeGuesser::getInstance(); + * $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file')); + * + * @author Bernhard Schussek + */ +class MimeTypeGuesser implements MimeTypeGuesserInterface +{ + /** + * The singleton instance. + * + * @var MimeTypeGuesser + */ + private static $instance = null; + + /** + * All registered MimeTypeGuesserInterface instances. + * + * @var array + */ + protected $guessers = array(); + + /** + * Returns the singleton instance. + * + * @return self + */ + public static function getInstance() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Resets the singleton instance. + */ + public static function reset() + { + self::$instance = null; + } + + /** + * Registers all natively provided mime type guessers. + */ + private function __construct() + { + if (FileBinaryMimeTypeGuesser::isSupported()) { + $this->register(new FileBinaryMimeTypeGuesser()); + } + + if (FileinfoMimeTypeGuesser::isSupported()) { + $this->register(new FileinfoMimeTypeGuesser()); + } + } + + /** + * Registers a new mime type guesser. + * + * When guessing, this guesser is preferred over previously registered ones. + * + * @param MimeTypeGuesserInterface $guesser + */ + public function register(MimeTypeGuesserInterface $guesser) + { + array_unshift($this->guessers, $guesser); + } + + /** + * Tries to guess the mime type of the given file. + * + * The file is passed to each registered mime type guesser in reverse order + * of their registration (last registered is queried first). Once a guesser + * returns a value that is not NULL, this method terminates and returns the + * value. + * + * @param string $path The path to the file + * + * @return string The mime type or NULL, if none could be guessed + * + * @throws \LogicException + * @throws FileNotFoundException + * @throws AccessDeniedException + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!$this->guessers) { + $msg = 'Unable to guess the mime type as no guessers are available'; + if (!FileinfoMimeTypeGuesser::isSupported()) { + $msg .= ' (Did you enable the php_fileinfo extension?)'; + } + throw new \LogicException($msg); + } + + foreach ($this->guessers as $guesser) { + if (null !== $mimeType = $guesser->guess($path)) { + return $mimeType; + } + } + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php new file mode 100644 index 00000000..f8c3ad22 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * Guesses the mime type of a file. + * + * @author Bernhard Schussek + */ +interface MimeTypeGuesserInterface +{ + /** + * Guesses the mime type of the file with the given path. + * + * @param string $path The path to the file + * + * @return string The mime type or NULL, if none could be guessed + * + * @throws FileNotFoundException If the file does not exist + * @throws AccessDeniedException If the file could not be read + */ + public function guess($path); +} diff --git a/vendor/symfony/http-foundation/File/Stream.php b/vendor/symfony/http-foundation/File/Stream.php new file mode 100644 index 00000000..69ae74c1 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Stream.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\HttpFoundation\File; + +/** + * A PHP stream of unknown size. + * + * @author Nicolas Grekas + */ +class Stream extends File +{ + /** + * {@inheritdoc} + */ + public function getSize() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/File/UploadedFile.php b/vendor/symfony/http-foundation/File/UploadedFile.php new file mode 100644 index 00000000..10837726 --- /dev/null +++ b/vendor/symfony/http-foundation/File/UploadedFile.php @@ -0,0 +1,293 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; + +/** + * A file uploaded through a form. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * @author Fabien Potencier + */ +class UploadedFile extends File +{ + /** + * Whether the test mode is activated. + * + * Local files are used in test mode hence the code should not enforce HTTP uploads. + * + * @var bool + */ + private $test = false; + + /** + * The original name of the uploaded file. + * + * @var string + */ + private $originalName; + + /** + * The mime type provided by the uploader. + * + * @var string + */ + private $mimeType; + + /** + * The file size provided by the uploader. + * + * @var int|null + */ + private $size; + + /** + * The UPLOAD_ERR_XXX constant provided by the uploader. + * + * @var int + */ + private $error; + + /** + * Accepts the information of the uploaded file as provided by the PHP global $_FILES. + * + * The file object is only created when the uploaded file is valid (i.e. when the + * isValid() method returns true). Otherwise the only methods that could be called + * on an UploadedFile instance are: + * + * * getClientOriginalName, + * * getClientMimeType, + * * isValid, + * * getError. + * + * Calling any other method on an non-valid instance will cause an unpredictable result. + * + * @param string $path The full temporary path to the file + * @param string $originalName The original file name + * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream + * @param int|null $size The file size + * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK + * @param bool $test Whether the test mode is active + * + * @throws FileException If file_uploads is disabled + * @throws FileNotFoundException If the file does not exist + */ + public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false) + { + $this->originalName = $this->getName($originalName); + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->size = $size; + $this->error = $error ?: UPLOAD_ERR_OK; + $this->test = (bool) $test; + + parent::__construct($path, UPLOAD_ERR_OK === $this->error); + } + + /** + * Returns the original file name. + * + * It is extracted from the request from which the file has been uploaded. + * Then it should not be considered as a safe value. + * + * @return string|null The original name + */ + public function getClientOriginalName() + { + return $this->originalName; + } + + /** + * Returns the original file extension. + * + * It is extracted from the original file name that was uploaded. + * Then it should not be considered as a safe value. + * + * @return string The extension + */ + public function getClientOriginalExtension() + { + return pathinfo($this->originalName, PATHINFO_EXTENSION); + } + + /** + * Returns the file mime type. + * + * The client mime type is extracted from the request from which the file + * was uploaded, so it should not be considered as a safe value. + * + * For a trusted mime type, use getMimeType() instead (which guesses the mime + * type based on the file content). + * + * @return string|null The mime type + * + * @see getMimeType() + */ + public function getClientMimeType() + { + return $this->mimeType; + } + + /** + * Returns the extension based on the client mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getClientMimeType() + * to guess the file extension. As such, the extension returned + * by this method cannot be trusted. + * + * For a trusted extension, use guessExtension() instead (which guesses + * the extension based on the guessed mime type for the file). + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @see guessExtension() + * @see getClientMimeType() + */ + public function guessClientExtension() + { + $type = $this->getClientMimeType(); + $guesser = ExtensionGuesser::getInstance(); + + return $guesser->guess($type); + } + + /** + * Returns the file size. + * + * It is extracted from the request from which the file has been uploaded. + * Then it should not be considered as a safe value. + * + * @return int|null The file size + */ + public function getClientSize() + { + return $this->size; + } + + /** + * Returns the upload error. + * + * If the upload was successful, the constant UPLOAD_ERR_OK is returned. + * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. + * + * @return int The upload error + */ + public function getError() + { + return $this->error; + } + + /** + * Returns whether the file was uploaded successfully. + * + * @return bool True if the file has been uploaded with HTTP and no error occurred + */ + public function isValid() + { + $isOk = $this->error === UPLOAD_ERR_OK; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @param string $directory The destination folder + * @param string $name The new file name + * + * @return File A File object representing the new file + * + * @throws FileException if, for any reason, the file could not have been moved + */ + public function move($directory, $name = null) + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + if (!@move_uploaded_file($this->getPathname(), $target)) { + $error = error_get_last(); + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * @return int The maximum size of an uploaded file in bytes + */ + public static function getMaxFilesize() + { + $iniMax = strtolower(ini_get('upload_max_filesize')); + + if ('' === $iniMax) { + return PHP_INT_MAX; + } + + $max = ltrim($iniMax, '+'); + if (0 === strpos($max, '0x')) { + $max = intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($iniMax, -1)) { + case 't': $max *= 1024; + case 'g': $max *= 1024; + case 'm': $max *= 1024; + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Returns an informative upload error message. + * + * @return string The error message regarding the specified error code + */ + public function getErrorMessage() + { + static $errors = array( + UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', + UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', + UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', + UPLOAD_ERR_NO_FILE => 'No file was uploaded.', + UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', + UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', + UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.', + ); + + $errorCode = $this->error; + $maxFilesize = $errorCode === UPLOAD_ERR_INI_SIZE ? self::getMaxFilesize() / 1024 : 0; + $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.'; + + return sprintf($message, $this->getClientOriginalName(), $maxFilesize); + } +} diff --git a/vendor/symfony/http-foundation/FileBag.php b/vendor/symfony/http-foundation/FileBag.php new file mode 100644 index 00000000..e17a9057 --- /dev/null +++ b/vendor/symfony/http-foundation/FileBag.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * FileBag is a container for uploaded files. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBag extends ParameterBag +{ + private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type'); + + /** + * Constructor. + * + * @param array $parameters An array of HTTP files + */ + public function __construct(array $parameters = array()) + { + $this->replace($parameters); + } + + /** + * {@inheritdoc} + */ + public function replace(array $files = array()) + { + $this->parameters = array(); + $this->add($files); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + if (!is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + + parent::set($key, $this->convertFileInformation($value)); + } + + /** + * {@inheritdoc} + */ + public function add(array $files = array()) + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + + /** + * Converts uploaded files to UploadedFile instances. + * + * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information + * + * @return UploadedFile|UploadedFile[] A (multi-dimensional) array of UploadedFile instances + */ + protected function convertFileInformation($file) + { + if ($file instanceof UploadedFile) { + return $file; + } + + $file = $this->fixPhpFilesArray($file); + if (is_array($file)) { + $keys = array_keys($file); + sort($keys); + + if ($keys == self::$fileKeys) { + if (UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']); + } + } else { + $file = array_map(array($this, 'convertFileInformation'), $file); + } + } + + return $file; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + * + * @param array $data + * + * @return array + */ + protected function fixPhpFilesArray($data) + { + if (!is_array($data)) { + return $data; + } + + $keys = array_keys($data); + sort($keys); + + if (self::$fileKeys != $keys || !isset($data['name']) || !is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::$fileKeys as $k) { + unset($files[$k]); + } + + foreach ($data['name'] as $key => $name) { + $files[$key] = $this->fixPhpFilesArray(array( + 'error' => $data['error'][$key], + 'name' => $name, + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key], + )); + } + + return $files; + } +} diff --git a/vendor/symfony/http-foundation/HeaderBag.php b/vendor/symfony/http-foundation/HeaderBag.php new file mode 100644 index 00000000..3cc9e702 --- /dev/null +++ b/vendor/symfony/http-foundation/HeaderBag.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HeaderBag is a container for HTTP headers. + * + * @author Fabien Potencier + */ +class HeaderBag implements \IteratorAggregate, \Countable +{ + protected $headers = array(); + protected $cacheControl = array(); + + /** + * Constructor. + * + * @param array $headers An array of HTTP headers + */ + public function __construct(array $headers = array()) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the headers as a string. + * + * @return string The headers + */ + public function __toString() + { + if (!$headers = $this->all()) { + return ''; + } + + ksort($headers); + $max = max(array_map('strlen', array_keys($headers))) + 1; + $content = ''; + foreach ($headers as $name => $values) { + $name = implode('-', array_map('ucfirst', explode('-', $name))); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); + } + } + + return $content; + } + + /** + * Returns the headers. + * + * @return array An array of headers + */ + public function all() + { + return $this->headers; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->all()); + } + + /** + * Replaces the current HTTP headers by a new set. + * + * @param array $headers An array of HTTP headers + */ + public function replace(array $headers = array()) + { + $this->headers = array(); + $this->add($headers); + } + + /** + * Adds new headers the current HTTP headers set. + * + * @param array $headers An array of HTTP headers + */ + public function add(array $headers) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns a header value by name. + * + * @param string $key The header name + * @param mixed $default The default value + * @param bool $first Whether to return the first value or all header values + * + * @return string|array The first header value if $first is true, an array of values otherwise + */ + public function get($key, $default = null, $first = true) + { + $key = str_replace('_', '-', strtolower($key)); + $headers = $this->all(); + + if (!array_key_exists($key, $headers)) { + if (null === $default) { + return $first ? null : array(); + } + + return $first ? $default : array($default); + } + + if ($first) { + return count($headers[$key]) ? $headers[$key][0] : $default; + } + + return $headers[$key]; + } + + /** + * Sets a header by name. + * + * @param string $key The key + * @param string|array $values The value or an array of values + * @param bool $replace Whether to replace the actual value or not (true by default) + */ + public function set($key, $values, $replace = true) + { + $key = str_replace('_', '-', strtolower($key)); + + $values = array_values((array) $values); + + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl($values[0]); + } + } + + /** + * Returns true if the HTTP header is defined. + * + * @param string $key The HTTP header + * + * @return bool true if the parameter exists, false otherwise + */ + public function has($key) + { + return array_key_exists(str_replace('_', '-', strtolower($key)), $this->all()); + } + + /** + * Returns true if the given HTTP header contains the given value. + * + * @param string $key The HTTP header name + * @param string $value The HTTP value + * + * @return bool true if the value is contained in the header, false otherwise + */ + public function contains($key, $value) + { + return in_array($value, $this->get($key, null, false)); + } + + /** + * Removes a header. + * + * @param string $key The HTTP header name + */ + public function remove($key) + { + $key = str_replace('_', '-', strtolower($key)); + + unset($this->headers[$key]); + + if ('cache-control' === $key) { + $this->cacheControl = array(); + } + } + + /** + * Returns the HTTP header value converted to a date. + * + * @param string $key The parameter key + * @param \DateTime $default The default value + * + * @return null|\DateTime The parsed DateTime or the default value if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + */ + public function getDate($key, \DateTime $default = null) + { + if (null === $value = $this->get($key)) { + return $default; + } + + if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) { + throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value)); + } + + return $date; + } + + /** + * Adds a custom Cache-Control directive. + * + * @param string $key The Cache-Control directive name + * @param mixed $value The Cache-Control directive value + */ + public function addCacheControlDirective($key, $value = true) + { + $this->cacheControl[$key] = $value; + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns true if the Cache-Control directive is defined. + * + * @param string $key The Cache-Control directive + * + * @return bool true if the directive exists, false otherwise + */ + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl); + } + + /** + * Returns a Cache-Control directive value by name. + * + * @param string $key The directive name + * + * @return mixed|null The directive value if defined, null otherwise + */ + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null; + } + + /** + * Removes a Cache-Control directive. + * + * @param string $key The Cache-Control directive + */ + public function removeCacheControlDirective($key) + { + unset($this->cacheControl[$key]); + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns an iterator for headers. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + + /** + * Returns the number of headers. + * + * @return int The number of headers + */ + public function count() + { + return count($this->headers); + } + + protected function getCacheControlHeader() + { + $parts = array(); + ksort($this->cacheControl); + foreach ($this->cacheControl as $key => $value) { + if (true === $value) { + $parts[] = $key; + } else { + if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { + $value = '"'.$value.'"'; + } + + $parts[] = "$key=$value"; + } + } + + return implode(', ', $parts); + } + + /** + * Parses a Cache-Control HTTP header. + * + * @param string $header The value of the Cache-Control HTTP header + * + * @return array An array representing the attribute values + */ + protected function parseCacheControl($header) + { + $cacheControl = array(); + preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true); + } + + return $cacheControl; + } +} diff --git a/vendor/symfony/http-foundation/IpUtils.php b/vendor/symfony/http-foundation/IpUtils.php new file mode 100644 index 00000000..28093be4 --- /dev/null +++ b/vendor/symfony/http-foundation/IpUtils.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Http utility functions. + * + * @author Fabien Potencier + */ +class IpUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. + * + * @param string $requestIp IP to check + * @param string|array $ips List of IPs or subnets (can be a string if only a single one) + * + * @return bool Whether the IP is valid + */ + public static function checkIp($requestIp, $ips) + { + if (!is_array($ips)) { + $ips = array($ips); + } + + $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4'; + + foreach ($ips as $ip) { + if (self::$method($requestIp, $ip)) { + return true; + } + } + + return false; + } + + /** + * Compares two IPv4 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @param string $requestIp IPv4 address to check + * @param string $ip IPv4 address or subnet in CIDR notation + * + * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet + */ + public static function checkIp4($requestIp, $ip) + { + if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return false; + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ($netmask === '0') { + return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + } + + if ($netmask < 0 || $netmask > 32) { + return false; + } + } else { + $address = $ip; + $netmask = 32; + } + + return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + } + + /** + * Compares two IPv6 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @author David Soria Parra + * + * @see https://github.com/dsp/v6tools + * + * @param string $requestIp IPv6 address to check + * @param string $ip IPv6 address or subnet in CIDR notation + * + * @return bool Whether the IP is valid + * + * @throws \RuntimeException When IPV6 support is not enabled + */ + public static function checkIp6($requestIp, $ip) + { + if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) { + throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ($netmask < 1 || $netmask > 128) { + return false; + } + } else { + $address = $ip; + $netmask = 128; + } + + $bytesAddr = unpack('n*', @inet_pton($address)); + $bytesTest = unpack('n*', @inet_pton($requestIp)); + + if (!$bytesAddr || !$bytesTest) { + return false; + } + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { + $left = $netmask - 16 * ($i - 1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xffff >> $left) & 0xffff; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/http-foundation/JsonResponse.php b/vendor/symfony/http-foundation/JsonResponse.php new file mode 100644 index 00000000..cf1a11ea --- /dev/null +++ b/vendor/symfony/http-foundation/JsonResponse.php @@ -0,0 +1,214 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response in JSON format. + * + * Note that this class does not force the returned JSON content to be an + * object. It is however recommended that you do return an object as it + * protects yourself against XSSI and JSON-JavaScript Hijacking. + * + * @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside + * + * @author Igor Wiedler + */ +class JsonResponse extends Response +{ + protected $data; + protected $callback; + + // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML. + // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + const DEFAULT_ENCODING_OPTIONS = 15; + + protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS; + + /** + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $json If the data is already a JSON string + */ + public function __construct($data = null, $status = 200, $headers = array(), $json = false) + { + parent::__construct('', $status, $headers); + + if (null === $data) { + $data = new \ArrayObject(); + } + + $json ? $this->setJson($data) : $this->setData($data); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::create($data, 200) + * ->setSharedMaxAge(300); + * + * @param mixed $data The json response data + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($data = null, $status = 200, $headers = array()) + { + return new static($data, $status, $headers); + } + + /** + * Make easier the creation of JsonResponse from raw json. + */ + public static function fromJsonString($data = null, $status = 200, $headers = array()) + { + return new static($data, $status, $headers, true); + } + + /** + * Sets the JSONP callback. + * + * @param string|null $callback The JSONP callback or null to use none + * + * @return $this + * + * @throws \InvalidArgumentException When the callback name is not valid + */ + public function setCallback($callback = null) + { + if (null !== $callback) { + // partially taken from http://www.geekality.net/2011/08/03/valid-javascript-identifier/ + // partially taken from https://github.com/willdurand/JsonpCallbackValidator + // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details. + // (c) William Durand + $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u'; + $reserved = array( + 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', + 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export', + 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false', + ); + $parts = explode('.', $callback); + foreach ($parts as $part) { + if (!preg_match($pattern, $part) || in_array($part, $reserved, true)) { + throw new \InvalidArgumentException('The callback name is not valid.'); + } + } + } + + $this->callback = $callback; + + return $this->update(); + } + + /** + * Sets a raw string containing a JSON document to be sent. + * + * @param string $json + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setJson($json) + { + $this->data = $json; + + return $this->update(); + } + + /** + * Sets the data to be sent as JSON. + * + * @param mixed $data + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setData($data = array()) + { + if (defined('HHVM_VERSION')) { + // HHVM does not trigger any warnings and let exceptions + // thrown from a JsonSerializable object pass through. + // If only PHP did the same... + $data = json_encode($data, $this->encodingOptions); + } else { + try { + // PHP 5.4 and up wrap exceptions thrown by JsonSerializable + // objects in a new exception that needs to be removed. + // Fortunately, PHP 5.5 and up do not trigger any warning anymore. + $data = json_encode($data, $this->encodingOptions); + } catch (\Exception $e) { + if ('Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; + } + throw $e; + } + } + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $this->setJson($data); + } + + /** + * Returns options used while encoding data to JSON. + * + * @return int + */ + public function getEncodingOptions() + { + return $this->encodingOptions; + } + + /** + * Sets options used while encoding data to JSON. + * + * @param int $encodingOptions + * + * @return $this + */ + public function setEncodingOptions($encodingOptions) + { + $this->encodingOptions = (int) $encodingOptions; + + return $this->setData(json_decode($this->data)); + } + + /** + * Updates the content and headers according to the JSON data and callback. + * + * @return $this + */ + protected function update() + { + if (null !== $this->callback) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data)); + } + + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + + return $this->setContent($this->data); + } +} diff --git a/vendor/symfony/http-foundation/LICENSE b/vendor/symfony/http-foundation/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/http-foundation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/http-foundation/ParameterBag.php b/vendor/symfony/http-foundation/ParameterBag.php new file mode 100644 index 00000000..c0b36479 --- /dev/null +++ b/vendor/symfony/http-foundation/ParameterBag.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ParameterBag is a container for key/value pairs. + * + * @author Fabien Potencier + */ +class ParameterBag implements \IteratorAggregate, \Countable +{ + /** + * Parameter storage. + * + * @var array + */ + protected $parameters; + + /** + * Constructor. + * + * @param array $parameters An array of parameters + */ + public function __construct(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Returns the parameters. + * + * @return array An array of parameters + */ + public function all() + { + return $this->parameters; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->parameters); + } + + /** + * Replaces the current parameters by a new set. + * + * @param array $parameters An array of parameters + */ + public function replace(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Adds parameters. + * + * @param array $parameters An array of parameters + */ + public function add(array $parameters = array()) + { + $this->parameters = array_replace($this->parameters, $parameters); + } + + /** + * Returns a parameter by name. + * + * @param string $key The key + * @param mixed $default The default value if the parameter key does not exist + * + * @return mixed + */ + public function get($key, $default = null) + { + return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; + } + + /** + * Sets a parameter by name. + * + * @param string $key The key + * @param mixed $value The value + */ + public function set($key, $value) + { + $this->parameters[$key] = $value; + } + + /** + * Returns true if the parameter is defined. + * + * @param string $key The key + * + * @return bool true if the parameter exists, false otherwise + */ + public function has($key) + { + return array_key_exists($key, $this->parameters); + } + + /** + * Removes a parameter. + * + * @param string $key The key + */ + public function remove($key) + { + unset($this->parameters[$key]); + } + + /** + * Returns the alphabetic characters of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * + * @return string The filtered value + */ + public function getAlpha($key, $default = '') + { + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the alphabetic characters and digits of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * + * @return string The filtered value + */ + public function getAlnum($key, $default = '') + { + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); + } + + /** + * Returns the digits of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * + * @return string The filtered value + */ + public function getDigits($key, $default = '') + { + // we need to remove - and + because they're allowed in the filter + return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT)); + } + + /** + * Returns the parameter value converted to integer. + * + * @param string $key The parameter key + * @param int $default The default value if the parameter key does not exist + * + * @return int The filtered value + */ + public function getInt($key, $default = 0) + { + return (int) $this->get($key, $default); + } + + /** + * Returns the parameter value converted to boolean. + * + * @param string $key The parameter key + * @param mixed $default The default value if the parameter key does not exist + * + * @return bool The filtered value + */ + public function getBoolean($key, $default = false) + { + return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN); + } + + /** + * Filter key. + * + * @param string $key Key + * @param mixed $default Default = null + * @param int $filter FILTER_* constant + * @param mixed $options Filter options + * + * @see http://php.net/manual/en/function.filter-var.php + * + * @return mixed + */ + public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array()) + { + $value = $this->get($key, $default); + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!is_array($options) && $options) { + $options = array('flags' => $options); + } + + // Add a convenience check for arrays. + if (is_array($value) && !isset($options['flags'])) { + $options['flags'] = FILTER_REQUIRE_ARRAY; + } + + return filter_var($value, $filter, $options); + } + + /** + * Returns an iterator for parameters. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->parameters); + } + + /** + * Returns the number of parameters. + * + * @return int The number of parameters + */ + public function count() + { + return count($this->parameters); + } +} diff --git a/vendor/symfony/http-foundation/README.md b/vendor/symfony/http-foundation/README.md new file mode 100644 index 00000000..8907f0b9 --- /dev/null +++ b/vendor/symfony/http-foundation/README.md @@ -0,0 +1,14 @@ +HttpFoundation Component +======================== + +The HttpFoundation component defines an object-oriented layer for the HTTP +specification. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/http-foundation/RedirectResponse.php b/vendor/symfony/http-foundation/RedirectResponse.php new file mode 100644 index 00000000..7435999a --- /dev/null +++ b/vendor/symfony/http-foundation/RedirectResponse.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\HttpFoundation; + +/** + * RedirectResponse represents an HTTP response doing a redirect. + * + * @author Fabien Potencier + */ +class RedirectResponse extends Response +{ + protected $targetUrl; + + /** + * Creates a redirect response so that it conforms to the rules defined for a redirect status code. + * + * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc., + * but practically every browser redirects on paths only as well + * @param int $status The status code (302 by default) + * @param array $headers The headers (Location is always set to the given URL) + * + * @throws \InvalidArgumentException + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3 + */ + public function __construct($url, $status = 302, $headers = array()) + { + parent::__construct('', $status, $headers); + + $this->setTargetUrl($url); + + if (!$this->isRedirect()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); + } + + if (301 == $status && !array_key_exists('cache-control', $headers)) { + $this->headers->remove('cache-control'); + } + } + + /** + * {@inheritdoc} + */ + public static function create($url = '', $status = 302, $headers = array()) + { + return new static($url, $status, $headers); + } + + /** + * Returns the target URL. + * + * @return string target URL + */ + public function getTargetUrl() + { + return $this->targetUrl; + } + + /** + * Sets the redirect target of this response. + * + * @param string $url The URL to redirect to + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setTargetUrl($url) + { + if (empty($url)) { + throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $this->targetUrl = $url; + + $this->setContent( + sprintf(' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + +', htmlspecialchars($url, ENT_QUOTES, 'UTF-8'))); + + $this->headers->set('Location', $url); + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/Request.php b/vendor/symfony/http-foundation/Request.php new file mode 100644 index 00000000..13ff1e05 --- /dev/null +++ b/vendor/symfony/http-foundation/Request.php @@ -0,0 +1,2104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Request represents an HTTP request. + * + * The methods dealing with URL accept / return a raw path (% encoded): + * * getBasePath + * * getBaseUrl + * * getPathInfo + * * getRequestUri + * * getUri + * * getUriForPath + * + * @author Fabien Potencier + */ +class Request +{ + const HEADER_FORWARDED = 0b00001; // When using RFC 7239 + const HEADER_X_FORWARDED_FOR = 0b00010; + const HEADER_X_FORWARDED_HOST = 0b00100; + const HEADER_X_FORWARDED_PROTO = 0b01000; + const HEADER_X_FORWARDED_PORT = 0b10000; + const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers + const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host + + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_IP = self::HEADER_X_FORWARDED_FOR; + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_HOST = self::HEADER_X_FORWARDED_HOST; + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_PROTO = self::HEADER_X_FORWARDED_PROTO; + /** @deprecated since version 3.3, to be removed in 4.0 */ + const HEADER_CLIENT_PORT = self::HEADER_X_FORWARDED_PORT; + + const METHOD_HEAD = 'HEAD'; + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_PATCH = 'PATCH'; + const METHOD_DELETE = 'DELETE'; + const METHOD_PURGE = 'PURGE'; + const METHOD_OPTIONS = 'OPTIONS'; + const METHOD_TRACE = 'TRACE'; + const METHOD_CONNECT = 'CONNECT'; + + /** + * @var string[] + */ + protected static $trustedProxies = array(); + + /** + * @var string[] + */ + protected static $trustedHostPatterns = array(); + + /** + * @var string[] + */ + protected static $trustedHosts = array(); + + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The FORWARDED header is the standard as of rfc7239. + * + * The other headers are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + * + * @deprecated since version 3.3, to be removed in 4.0 + */ + protected static $trustedHeaders = array( + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ); + + protected static $httpMethodParameterOverride = false; + + /** + * Custom parameters. + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $attributes; + + /** + * Request body parameters ($_POST). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $request; + + /** + * Query string parameters ($_GET). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $query; + + /** + * Server and execution environment parameters ($_SERVER). + * + * @var \Symfony\Component\HttpFoundation\ServerBag + */ + public $server; + + /** + * Uploaded files ($_FILES). + * + * @var \Symfony\Component\HttpFoundation\FileBag + */ + public $files; + + /** + * Cookies ($_COOKIE). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $cookies; + + /** + * Headers (taken from the $_SERVER). + * + * @var \Symfony\Component\HttpFoundation\HeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var array + */ + protected $languages; + + /** + * @var array + */ + protected $charsets; + + /** + * @var array + */ + protected $encodings; + + /** + * @var array + */ + protected $acceptableContentTypes; + + /** + * @var string + */ + protected $pathInfo; + + /** + * @var string + */ + protected $requestUri; + + /** + * @var string + */ + protected $baseUrl; + + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $method; + + /** + * @var string + */ + protected $format; + + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + /** + * @var string + */ + protected $locale; + + /** + * @var string + */ + protected $defaultLocale = 'en'; + + /** + * @var array + */ + protected static $formats; + + protected static $requestFactory; + + private $isHostValid = true; + private $isClientIpsValid = true; + private $isForwardedValid = true; + + private static $trustedHeaderSet = -1; + + /** @deprecated since version 3.3, to be removed in 4.0 */ + private static $trustedHeaderNames = array( + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ); + + private static $forwardedParams = array( + self::HEADER_X_FORWARDED_FOR => 'for', + self::HEADER_X_FORWARDED_HOST => 'host', + self::HEADER_X_FORWARDED_PROTO => 'proto', + self::HEADER_X_FORWARDED_PORT => 'host', + ); + + /** + * Constructor. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource $content The raw body data + */ + public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource $content The raw body data + */ + public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->request = new ParameterBag($request); + $this->query = new ParameterBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new ParameterBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->encodings = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + + /** + * Creates a new request with values from PHP's super globals. + * + * @return static + */ + public static function createFromGlobals() + { + // With the php's bug #66606, the php's built-in web server + // stores the Content-Type and Content-Length header values in + // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields. + $server = $_SERVER; + if ('cli-server' === PHP_SAPI) { + if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) { + $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH']; + } + if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) { + $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE']; + } + } + + $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server); + + if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') + && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH')) + ) { + parse_str($request->getContent(), $data); + $request->request = new ParameterBag($data); + } + + return $request; + } + + /** + * Creates a Request based on a given URI and configuration. + * + * The information contained in the URI always take precedence + * over the other information (server and parameters). + * + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string $content The raw body data + * + * @return static + */ + public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null) + { + $server = array_replace(array( + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony/3.X', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'REMOTE_ADDR' => '127.0.0.1', + 'SCRIPT_NAME' => '', + 'SCRIPT_FILENAME' => '', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_TIME' => time(), + ), $server); + + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + + $components = parse_url($uri); + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'].':'.$components['port']; + } + + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + + if (!isset($components['path'])) { + $components['path'] = '/'; + } + + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + // no break + case 'PATCH': + $request = $parameters; + $query = array(); + break; + default: + $request = array(); + $query = $parameters; + break; + } + + $queryString = ''; + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + + if ($query) { + $query = array_replace($qs, $query); + $queryString = http_build_query($query, '', '&'); + } else { + $query = $qs; + $queryString = $components['query']; + } + } elseif ($query) { + $queryString = http_build_query($query, '', '&'); + } + + $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : ''); + $server['QUERY_STRING'] = $queryString; + + return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content); + } + + /** + * Sets a callable able to create a Request instance. + * + * This is mainly useful when you need to override the Request class + * to keep BC with an existing system. It should not be used for any + * other purpose. + * + * @param callable|null $callable A PHP callable + */ + public static function setFactory($callable) + { + self::$requestFactory = $callable; + } + + /** + * Clones a request and overrides some of its parameters. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * + * @return static + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + $dup = clone $this; + if ($query !== null) { + $dup->query = new ParameterBag($query); + } + if ($request !== null) { + $dup->request = new ParameterBag($request); + } + if ($attributes !== null) { + $dup->attributes = new ParameterBag($attributes); + } + if ($cookies !== null) { + $dup->cookies = new ParameterBag($cookies); + } + if ($files !== null) { + $dup->files = new FileBag($files); + } + if ($server !== null) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->encodings = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + + if (!$dup->get('_format') && $this->get('_format')) { + $dup->attributes->set('_format', $this->get('_format')); + } + + if (!$dup->getRequestFormat(null)) { + $dup->setRequestFormat($this->getRequestFormat(null)); + } + + return $dup; + } + + /** + * Clones the current request. + * + * Note that the session is not cloned as duplicated requests + * are most of the time sub-requests of the main one. + */ + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + + /** + * Returns the request as a string. + * + * @return string The request + */ + public function __toString() + { + try { + $content = $this->getContent(); + } catch (\LogicException $e) { + return trigger_error($e, E_USER_ERROR); + } + + return + sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". + $this->headers."\r\n". + $content; + } + + /** + * Overrides the PHP global variables according to this request instance. + * + * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. + * $_FILES is never overridden, see rfc1867 + */ + public function overrideGlobals() + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), null, '&'))); + + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_'.$key] = implode(', ', $value); + } + } + + $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE); + + $requestOrder = ini_get('request_order') ?: ini_get('variables_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + + $_REQUEST = array(); + foreach (str_split($requestOrder) as $order) { + $_REQUEST = array_merge($_REQUEST, $request[$order]); + } + } + + /** + * Sets a list of trusted proxies. + * + * You should only list the reverse proxies that you manage directly. + * + * @param array $proxies A list of trusted proxies + * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies + * + * @throws \InvalidArgumentException When $trustedHeaderSet is invalid + */ + public static function setTrustedProxies(array $proxies/*, int $trustedHeaderSet*/) + { + self::$trustedProxies = $proxies; + + if (2 > func_num_args()) { + @trigger_error(sprintf('The %s() method expects a bit field of Request::HEADER_* as second argument since version 3.3. Defining it will be required in 4.0. ', __METHOD__), E_USER_DEPRECATED); + + return; + } + $trustedHeaderSet = (int) func_get_arg(1); + + foreach (self::$trustedHeaderNames as $header => $name) { + self::$trustedHeaders[$header] = $header & $trustedHeaderSet ? $name : null; + } + self::$trustedHeaderSet = $trustedHeaderSet; + } + + /** + * Gets the list of trusted proxies. + * + * @return array An array of trusted proxies + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + + /** + * Gets the set of trusted headers from trusted proxies. + * + * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies + */ + public static function getTrustedHeaderSet() + { + return self::$trustedHeaderSet; + } + + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('#%s#i', $hostPattern); + }, $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = array(); + } + + /** + * Gets the list of trusted host patterns. + * + * @return array An array of trusted host patterns + */ + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + + /** + * Sets the name for trusted headers. + * + * The following header keys are supported: + * + * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp()) + * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getHost()) + * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort()) + * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure()) + * * Request::HEADER_FORWARDED: defaults to Forwarded (see RFC 7239) + * + * Setting an empty value allows to disable the trusted header for the given key. + * + * @param string $key The header key + * @param string $value The header name + * + * @throws \InvalidArgumentException + * + * @deprecated since version 3.3, to be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead. + */ + public static function setTrustedHeaderName($key, $value) + { + @trigger_error(sprintf('The "%s()" method is deprecated since version 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', __METHOD__), E_USER_DEPRECATED); + + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key)); + } + + self::$trustedHeaders[$key] = $value; + + if (null !== $value) { + self::$trustedHeaderNames[$key] = $value; + self::$trustedHeaderSet |= $key; + } else { + self::$trustedHeaderSet &= ~$key; + } + } + + /** + * Gets the trusted proxy header name. + * + * @param string $key The header key + * + * @return string The header name + * + * @throws \InvalidArgumentException + * + * @deprecated since version 3.3, to be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead. + */ + public static function getTrustedHeaderName($key) + { + if (2 > func_num_args() || func_get_arg(1)) { + @trigger_error(sprintf('The "%s()" method is deprecated since version 3.3 and will be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.', __METHOD__), E_USER_DEPRECATED); + } + + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key)); + } + + return self::$trustedHeaders[$key]; + } + + /** + * Normalizes a query string. + * + * It builds a normalized query string, where keys/value pairs are alphabetized, + * have consistent escaping and unneeded delimiters are removed. + * + * @param string $qs Query string + * + * @return string A normalized query string for the Request + */ + public static function normalizeQueryString($qs) + { + if ('' == $qs) { + return ''; + } + + $parts = array(); + $order = array(); + + foreach (explode('&', $qs) as $param) { + if ('' === $param || '=' === $param[0]) { + // Ignore useless delimiters, e.g. "x=y&". + // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway. + // PHP also does not include them when building _GET. + continue; + } + + $keyValuePair = explode('=', $param, 2); + + // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded). + // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to + // RFC 3986 with rawurlencode. + $parts[] = isset($keyValuePair[1]) ? + rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) : + rawurlencode(urldecode($keyValuePair[0])); + $order[] = urldecode($keyValuePair[0]); + } + + array_multisort($order, SORT_ASC, $parts); + + return implode('&', $parts); + } + + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered + * and used to send a "PUT" or "DELETE" request via the _method request parameter. + * If these methods are not protected against CSRF, this presents a possible vulnerability. + * + * The HTTP method can only be overridden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + + /** + * Checks whether support for the _method request parameter is enabled. + * + * @return bool True when the _method request parameter is enabled, false otherwise + */ + public static function getHttpMethodParameterOverride() + { + return self::$httpMethodParameterOverride; + } + + /** + * Gets a "parameter" value from any bag. + * + * This method is mainly useful for libraries that want to provide some flexibility. If you don't need the + * flexibility in controllers, it is better to explicitly get request parameters from the appropriate + * public property instead (attributes, query, request). + * + * Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY + * + * @param string $key the key + * @param mixed $default the default value if the parameter key does not exist + * + * @return mixed + */ + public function get($key, $default = null) + { + if ($this !== $result = $this->attributes->get($key, $this)) { + return $result; + } + + if ($this !== $result = $this->query->get($key, $this)) { + return $result; + } + + if ($this !== $result = $this->request->get($key, $this)) { + return $result; + } + + return $default; + } + + /** + * Gets the Session. + * + * @return SessionInterface|null The session + */ + public function getSession() + { + return $this->session; + } + + /** + * Whether the request contains a Session which was started in one of the + * previous requests. + * + * @return bool + */ + public function hasPreviousSession() + { + // the check for $this->session avoids malicious users trying to fake a session cookie with proper name + return $this->hasSession() && $this->cookies->has($this->session->getName()); + } + + /** + * Whether the request contains a Session object. + * + * This method does not give any information about the state of the session object, + * like whether the session is started or not. It is just a way to check if this Request + * is associated with a Session instance. + * + * @return bool true when the Request contains a Session object, false otherwise + */ + public function hasSession() + { + return null !== $this->session; + } + + /** + * Sets the Session. + * + * @param SessionInterface $session The Session + */ + public function setSession(SessionInterface $session) + { + $this->session = $session; + } + + /** + * Returns the client IP addresses. + * + * In the returned array the most trusted IP address is first, and the + * least trusted one last. The "real" client IP address is the last one, + * but this is also the least trusted one. Trusted proxies are stripped. + * + * Use this method carefully; you should use getClientIp() instead. + * + * @return array The client IP addresses + * + * @see getClientIp() + */ + public function getClientIps() + { + $ip = $this->server->get('REMOTE_ADDR'); + + if (!$this->isFromTrustedProxy()) { + return array($ip); + } + + return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip); + } + + /** + * Returns the client IP address. + * + * This method can read the client IP address from the "X-Forwarded-For" header + * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" + * header value is a comma+space separated list of IP addresses, the left-most + * being the original client, and each successive proxy that passed the request + * adding the IP address where it received the request from. + * + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return string|null The client IP address + * + * @see getClientIps() + * @see http://en.wikipedia.org/wiki/X-Forwarded-For + */ + public function getClientIp() + { + $ipAddresses = $this->getClientIps(); + + return $ipAddresses[0]; + } + + /** + * Returns current script name. + * + * @return string + */ + public function getScriptName() + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + + /** + * Returns the path being requested relative to the executed script. + * + * The path info always starts with a /. + * + * Suppose this request is instantiated from /mysite on localhost: + * + * * http://localhost/mysite returns an empty string + * * http://localhost/mysite/about returns '/about' + * * http://localhost/mysite/enco%20ded returns '/enco%20ded' + * * http://localhost/mysite/about?var=1 returns '/about' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getPathInfo() + { + if (null === $this->pathInfo) { + $this->pathInfo = $this->preparePathInfo(); + } + + return $this->pathInfo; + } + + /** + * Returns the root path from which this request is executed. + * + * Suppose that an index.php file instantiates this request object: + * + * * http://localhost/index.php returns an empty string + * * http://localhost/index.php/page returns an empty string + * * http://localhost/web/index.php returns '/web' + * * http://localhost/we%20b/index.php returns '/we%20b' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getBasePath() + { + if (null === $this->basePath) { + $this->basePath = $this->prepareBasePath(); + } + + return $this->basePath; + } + + /** + * Returns the root URL from which this request is executed. + * + * The base URL never ends with a /. + * + * This is similar to getBasePath(), except that it also includes the + * script filename (e.g. index.php) if one exists. + * + * @return string The raw URL (i.e. not urldecoded) + */ + public function getBaseUrl() + { + if (null === $this->baseUrl) { + $this->baseUrl = $this->prepareBaseUrl(); + } + + return $this->baseUrl; + } + + /** + * Gets the request's scheme. + * + * @return string + */ + public function getScheme() + { + return $this->isSecure() ? 'https' : 'http'; + } + + /** + * Returns the port on which the request is made. + * + * This method can read the client port from the "X-Forwarded-Port" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Port" header must contain the client port. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Port", + * configure it via via the $trustedHeaderSet argument of the + * Request::setTrustedProxies() method instead. + * + * @return int|string can be a string if fetched from the server bag + */ + public function getPort() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) { + $host = $host[0]; + } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + return $this->server->get('SERVER_PORT'); + } + + if ($host[0] === '[') { + $pos = strpos($host, ':', strrpos($host, ']')); + } else { + $pos = strrpos($host, ':'); + } + + if (false !== $pos) { + return (int) substr($host, $pos + 1); + } + + return 'https' === $this->getScheme() ? 443 : 80; + } + + /** + * Returns the user. + * + * @return string|null + */ + public function getUser() + { + return $this->headers->get('PHP_AUTH_USER'); + } + + /** + * Returns the password. + * + * @return string|null + */ + public function getPassword() + { + return $this->headers->get('PHP_AUTH_PW'); + } + + /** + * Gets the user info. + * + * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server + */ + public function getUserInfo() + { + $userinfo = $this->getUser(); + + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":$pass"; + } + + return $userinfo; + } + + /** + * Returns the HTTP host being requested. + * + * The port name will be appended to the host if it's non-standard. + * + * @return string + */ + public function getHttpHost() + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + + if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) { + return $this->getHost(); + } + + return $this->getHost().':'.$port; + } + + /** + * Returns the requested URI (path and query string). + * + * @return string The raw URI (i.e. not URI decoded) + */ + public function getRequestUri() + { + if (null === $this->requestUri) { + $this->requestUri = $this->prepareRequestUri(); + } + + return $this->requestUri; + } + + /** + * Gets the scheme and HTTP host. + * + * If the URL was called with basic authentication, the user + * and the password are not added to the generated string. + * + * @return string The scheme and HTTP host + */ + public function getSchemeAndHttpHost() + { + return $this->getScheme().'://'.$this->getHttpHost(); + } + + /** + * Generates a normalized URI (URL) for the Request. + * + * @return string A normalized URI (URL) for the Request + * + * @see getQueryString() + */ + public function getUri() + { + if (null !== $qs = $this->getQueryString()) { + $qs = '?'.$qs; + } + + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; + } + + /** + * Generates a normalized URI for the given path. + * + * @param string $path A path to use instead of the current one + * + * @return string The normalized URI for the path + */ + public function getUriForPath($path) + { + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; + } + + /** + * Returns the path as relative reference from the current Request path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @param string $path The target path + * + * @return string The relative target path + */ + public function getRelativeUriForPath($path) + { + // be sure that we are dealing with an absolute path + if (!isset($path[0]) || '/' !== $path[0]) { + return $path; + } + + if ($path === $basePath = $this->getPathInfo()) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see http://tools.ietf.org/html/rfc3986#section-4.2). + return !isset($path[0]) || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Generates the normalized query string for the Request. + * + * It builds a normalized query string, where keys/value pairs are alphabetized + * and have consistent escaping. + * + * @return string|null A normalized query string for the Request + */ + public function getQueryString() + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + + return '' === $qs ? null : $qs; + } + + /** + * Checks whether the request is secure or not. + * + * This method can read the client protocol from the "X-Forwarded-Proto" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". + * + * If your reverse proxy uses a different header name than "X-Forwarded-Proto" + * ("SSL_HTTPS" for instance), configure it via the $trustedHeaderSet + * argument of the Request::setTrustedProxies() method instead. + * + * @return bool + */ + public function isSecure() + { + if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) { + return in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true); + } + + $https = $this->server->get('HTTPS'); + + return !empty($https) && 'off' !== strtolower($https); + } + + /** + * Returns the host name. + * + * This method can read the client host name from the "X-Forwarded-Host" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Host" header must contain the client host name. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Host", + * configure it via the $trustedHeaderSet argument of the + * Request::setTrustedProxies() method instead. + * + * @return string + * + * @throws SuspiciousOperationException when the host name is invalid or not trusted + */ + public function getHost() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + if (!$host = $this->server->get('SERVER_NAME')) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) + // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) + // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names + if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host)); + } + + if (count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (in_array($host, self::$trustedHosts)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + if (!$this->isHostValid) { + return ''; + } + $this->isHostValid = false; + + throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host)); + } + + return $host; + } + + /** + * Sets the request method. + * + * @param string $method + */ + public function setMethod($method) + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + + /** + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. + * + * The method is always an uppercased string. + * + * @return string The request method + * + * @see getRealMethod() + */ + public function getMethod() + { + if (null === $this->method) { + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + + if ('POST' === $this->method) { + if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) { + $this->method = strtoupper($method); + } elseif (self::$httpMethodParameterOverride) { + $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST'))); + } + } + } + + return $this->method; + } + + /** + * Gets the "real" request method. + * + * @return string The request method + * + * @see getMethod() + */ + public function getRealMethod() + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + + /** + * Gets the mime type associated with the format. + * + * @param string $format The format + * + * @return string The associated mime type (null if not found) + */ + public function getMimeType($format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + + /** + * Gets the mime types associated with the format. + * + * @param string $format The format + * + * @return array The associated mime types + */ + public static function getMimeTypes($format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format] : array(); + } + + /** + * Gets the format associated with the mime type. + * + * @param string $mimeType The associated mime type + * + * @return string|null The format (null if not found) + */ + public function getFormat($mimeType) + { + $canonicalMimeType = null; + if (false !== $pos = strpos($mimeType, ';')) { + $canonicalMimeType = substr($mimeType, 0, $pos); + } + + if (null === static::$formats) { + static::initializeFormats(); + } + + foreach (static::$formats as $format => $mimeTypes) { + if (in_array($mimeType, (array) $mimeTypes)) { + return $format; + } + if (null !== $canonicalMimeType && in_array($canonicalMimeType, (array) $mimeTypes)) { + return $format; + } + } + } + + /** + * Associates a format with mime types. + * + * @param string $format The format + * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + */ + public function setFormat($format, $mimeTypes) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes); + } + + /** + * Gets the request format. + * + * Here is the process to determine the format: + * + * * format defined by the user (with setRequestFormat()) + * * _format request attribute + * * $default + * + * @param string $default The default format + * + * @return string The request format + */ + public function getRequestFormat($default = 'html') + { + if (null === $this->format) { + $this->format = $this->attributes->get('_format'); + } + + return null === $this->format ? $default : $this->format; + } + + /** + * Sets the request format. + * + * @param string $format The request format + */ + public function setRequestFormat($format) + { + $this->format = $format; + } + + /** + * Gets the format associated with the request. + * + * @return string|null The format (null if no content type is present) + */ + public function getContentType() + { + return $this->getFormat($this->headers->get('CONTENT_TYPE')); + } + + /** + * Sets the default locale. + * + * @param string $locale + */ + public function setDefaultLocale($locale) + { + $this->defaultLocale = $locale; + + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + + /** + * Get the default locale. + * + * @return string + */ + public function getDefaultLocale() + { + return $this->defaultLocale; + } + + /** + * Sets the locale. + * + * @param string $locale + */ + public function setLocale($locale) + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + + /** + * Get the locale. + * + * @return string + */ + public function getLocale() + { + return null === $this->locale ? $this->defaultLocale : $this->locale; + } + + /** + * Checks if the request method is of specified type. + * + * @param string $method Uppercase request method (GET, POST etc) + * + * @return bool + */ + public function isMethod($method) + { + return $this->getMethod() === strtoupper($method); + } + + /** + * Checks whether or not the method is safe. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 + * + * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default. + * + * @return bool + */ + public function isMethodSafe(/* $andCacheable = true */) + { + if (!func_num_args() || func_get_arg(0)) { + // This deprecation should be turned into a BadMethodCallException in 4.0 (without adding the argument in the signature) + // then setting $andCacheable to false should be deprecated in 4.1 + @trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since version 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', E_USER_DEPRECATED); + + return in_array($this->getMethod(), array('GET', 'HEAD')); + } + + return in_array($this->getMethod(), array('GET', 'HEAD', 'OPTIONS', 'TRACE')); + } + + /** + * Checks whether or not the method is idempotent. + * + * @return bool + */ + public function isMethodIdempotent() + { + return in_array($this->getMethod(), array('HEAD', 'GET', 'PUT', 'DELETE', 'TRACE', 'OPTIONS', 'PURGE')); + } + + /** + * Checks whether the method is cacheable or not. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 + * + * @return bool + */ + public function isMethodCacheable() + { + return in_array($this->getMethod(), array('GET', 'HEAD')); + } + + /** + * Returns the request body content. + * + * @param bool $asResource If true, a resource will be returned + * + * @return string|resource The request body content or a resource to read the body stream + * + * @throws \LogicException + */ + public function getContent($asResource = false) + { + $currentContentIsResource = is_resource($this->content); + if (\PHP_VERSION_ID < 50600 && false === $this->content) { + throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.'); + } + + if (true === $asResource) { + if ($currentContentIsResource) { + rewind($this->content); + + return $this->content; + } + + // Content passed in parameter (test) + if (is_string($this->content)) { + $resource = fopen('php://temp', 'r+'); + fwrite($resource, $this->content); + rewind($resource); + + return $resource; + } + + $this->content = false; + + return fopen('php://input', 'rb'); + } + + if ($currentContentIsResource) { + rewind($this->content); + + return stream_get_contents($this->content); + } + + if (null === $this->content || false === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * Gets the Etags. + * + * @return array The entity tags + */ + public function getETags() + { + return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY); + } + + /** + * @return bool + */ + public function isNoCache() + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + + /** + * Returns the preferred language. + * + * @param array $locales An array of ordered available locales + * + * @return string|null The preferred locale + */ + public function getPreferredLanguage(array $locales = null) + { + $preferredLanguages = $this->getLanguages(); + + if (empty($locales)) { + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null; + } + + if (!$preferredLanguages) { + return $locales[0]; + } + + $extendedPreferredLanguages = array(); + foreach ($preferredLanguages as $language) { + $extendedPreferredLanguages[] = $language; + if (false !== $position = strpos($language, '_')) { + $superLanguage = substr($language, 0, $position); + if (!in_array($superLanguage, $preferredLanguages)) { + $extendedPreferredLanguages[] = $superLanguage; + } + } + } + + $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); + + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0]; + } + + /** + * Gets a list of languages acceptable by the client browser. + * + * @return array Languages ordered in the user browser preferences + */ + public function getLanguages() + { + if (null !== $this->languages) { + return $this->languages; + } + + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = array(); + foreach ($languages as $lang => $acceptHeaderItem) { + if (false !== strpos($lang, '-')) { + $codes = explode('-', $lang); + if ('i' === $codes[0]) { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registered with the + // i-prefix, such as i-cherokee + if (count($codes) > 1) { + $lang = $codes[1]; + } + } else { + for ($i = 0, $max = count($codes); $i < $max; ++$i) { + if ($i === 0) { + $lang = strtolower($codes[0]); + } else { + $lang .= '_'.strtoupper($codes[$i]); + } + } + } + } + + $this->languages[] = $lang; + } + + return $this->languages; + } + + /** + * Gets a list of charsets acceptable by the client browser. + * + * @return array List of charsets in preferable order + */ + public function getCharsets() + { + if (null !== $this->charsets) { + return $this->charsets; + } + + return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); + } + + /** + * Gets a list of encodings acceptable by the client browser. + * + * @return array List of encodings in preferable order + */ + public function getEncodings() + { + if (null !== $this->encodings) { + return $this->encodings; + } + + return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()); + } + + /** + * Gets a list of content types acceptable by the client browser. + * + * @return array List of content types in preferable order + */ + public function getAcceptableContentTypes() + { + if (null !== $this->acceptableContentTypes) { + return $this->acceptableContentTypes; + } + + return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); + } + + /** + * Returns true if the request is a XMLHttpRequest. + * + * It works if your JavaScript library sets an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: + * + * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript + * + * @return bool true if the request is an XMLHttpRequest, false otherwise + */ + public function isXmlHttpRequest() + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + /* + * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) + * + * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + + protected function prepareRequestUri() + { + $requestUri = ''; + + if ($this->headers->has('X_ORIGINAL_URL')) { + // IIS with Microsoft Rewrite Module + $requestUri = $this->headers->get('X_ORIGINAL_URL'); + $this->headers->remove('X_ORIGINAL_URL'); + $this->server->remove('HTTP_X_ORIGINAL_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->headers->has('X_REWRITE_URL')) { + // IIS with ISAPI_Rewrite + $requestUri = $this->headers->get('X_REWRITE_URL'); + $this->headers->remove('X_REWRITE_URL'); + } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') { + // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path + $schemeAndHttpHost = $this->getSchemeAndHttpHost(); + if (strpos($requestUri, $schemeAndHttpHost) === 0) { + $requestUri = substr($requestUri, strlen($schemeAndHttpHost)); + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + // IIS 5.0, PHP as CGI + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?'.$this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + + return $requestUri; + } + + /** + * Prepares the base URL. + * + * @return string + */ + protected function prepareBaseUrl() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + + if (basename($this->server->get('SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/'.$seg.$baseUrl; + ++$index; + } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + // full $baseUrl matches + return $prefix; + } + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/'.DIRECTORY_SEPARATOR).'/')) { + // directory portion of $baseUrl matches + return rtrim($prefix, '/'.DIRECTORY_SEPARATOR); + } + + $truncatedRequestUri = $requestUri; + if (false !== $pos = strpos($requestUri, '?')) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + // no match whatsoever; set it blank + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && $pos !== 0) { + $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); + } + + return rtrim($baseUrl, '/'.DIRECTORY_SEPARATOR); + } + + /** + * Prepares the base path. + * + * @return string base path + */ + protected function prepareBasePath() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + return ''; + } + + if (basename($baseUrl) === $filename) { + $basePath = dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + + return rtrim($basePath, '/'); + } + + /** + * Prepares the path info. + * + * @return string path info + */ + protected function preparePathInfo() + { + $baseUrl = $this->getBaseUrl(); + + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + + // Remove the query string from REQUEST_URI + if ($pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + + $pathInfo = substr($requestUri, strlen($baseUrl)); + if (null !== $baseUrl && (false === $pathInfo || '' === $pathInfo)) { + // If substr() returns false then PATH_INFO is set to an empty string + return '/'; + } elseif (null === $baseUrl) { + return $requestUri; + } + + return (string) $pathInfo; + } + + /** + * Initializes HTTP request formats. + */ + protected static function initializeFormats() + { + static::$formats = array( + 'html' => array('text/html', 'application/xhtml+xml'), + 'txt' => array('text/plain'), + 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'), + 'css' => array('text/css'), + 'json' => array('application/json', 'application/x-json'), + 'xml' => array('text/xml', 'application/xml', 'application/x-xml'), + 'rdf' => array('application/rdf+xml'), + 'atom' => array('application/atom+xml'), + 'rss' => array('application/rss+xml'), + 'form' => array('application/x-www-form-urlencoded'), + ); + } + + /** + * Sets the default PHP locale. + * + * @param string $locale + */ + private function setPhpDefaultLocale($locale) + { + // if either the class Locale doesn't exist, or an exception is thrown when + // setting the default locale, the intl module is not installed, and + // the call can be ignored: + try { + if (class_exists('Locale', false)) { + \Locale::setDefault($locale); + } + } catch (\Exception $e) { + } + } + + /* + * Returns the prefix as encoded in the string when the string starts with + * the given prefix, false otherwise. + * + * @param string $string The urlencoded string + * @param string $prefix The prefix not encoded + * + * @return string|false The prefix as it is encoded in $string, or false + */ + private function getUrlencodedPrefix($string, $prefix) + { + if (0 !== strpos(rawurldecode($string), $prefix)) { + return false; + } + + $len = strlen($prefix); + + if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { + return $match[0]; + } + + return false; + } + + private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + if (self::$requestFactory) { + $request = call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content); + + if (!$request instanceof self) { + throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); + } + + return $request; + } + + return new static($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Indicates whether this request originated from a trusted proxy. + * + * This can be useful to determine whether or not to trust the + * contents of a proxy-specific header. + * + * @return bool true if the request came from a trusted proxy, false otherwise + */ + public function isFromTrustedProxy() + { + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies); + } + + private function getTrustedValues($type, $ip = null) + { + $clientValues = array(); + $forwardedValues = array(); + + if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) { + foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) { + $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v); + } + } + + if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) { + $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]); + $forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array(); + } + + if (null !== $ip) { + $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip); + $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip); + } + + if ($forwardedValues === $clientValues || !$clientValues) { + return $forwardedValues; + } + + if (!$forwardedValues) { + return $clientValues; + } + + if (!$this->isForwardedValid) { + return null !== $ip ? array('0.0.0.0', $ip) : array(); + } + $this->isForwardedValid = false; + + throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type])); + } + + private function normalizeAndFilterClientIps(array $clientIps, $ip) + { + if (!$clientIps) { + return array(); + } + $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from + $firstTrustedIp = null; + + foreach ($clientIps as $key => $clientIp) { + // Remove port (unfortunately, it does happen) + if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) { + $clientIps[$key] = $clientIp = $match[1]; + } + + if (!filter_var($clientIp, FILTER_VALIDATE_IP)) { + unset($clientIps[$key]); + + continue; + } + + if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { + unset($clientIps[$key]); + + // Fallback to this when the client IP falls into the range of trusted proxies + if (null === $firstTrustedIp) { + $firstTrustedIp = $clientIp; + } + } + } + + // Now the IP chain contains only untrusted proxies and the client IP + return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher.php new file mode 100644 index 00000000..aa4f67b5 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcher compares a pre-defined set of checks against a Request instance. + * + * @author Fabien Potencier + */ +class RequestMatcher implements RequestMatcherInterface +{ + /** + * @var string|null + */ + private $path; + + /** + * @var string|null + */ + private $host; + + /** + * @var string[] + */ + private $methods = array(); + + /** + * @var string[] + */ + private $ips = array(); + + /** + * @var array + */ + private $attributes = array(); + + /** + * @var string[] + */ + private $schemes = array(); + + /** + * @param string|null $path + * @param string|null $host + * @param string|string[]|null $methods + * @param string|string[]|null $ips + * @param array $attributes + * @param string|string[]|null $schemes + */ + public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = array(), $schemes = null) + { + $this->matchPath($path); + $this->matchHost($host); + $this->matchMethod($methods); + $this->matchIps($ips); + $this->matchScheme($schemes); + + foreach ($attributes as $k => $v) { + $this->matchAttribute($k, $v); + } + } + + /** + * Adds a check for the HTTP scheme. + * + * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes + */ + public function matchScheme($scheme) + { + $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : array(); + } + + /** + * Adds a check for the URL host name. + * + * @param string|null $regexp A Regexp + */ + public function matchHost($regexp) + { + $this->host = $regexp; + } + + /** + * Adds a check for the URL path info. + * + * @param string|null $regexp A Regexp + */ + public function matchPath($regexp) + { + $this->path = $regexp; + } + + /** + * Adds a check for the client IP. + * + * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIp($ip) + { + $this->matchIps($ip); + } + + /** + * Adds a check for the client IP. + * + * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIps($ips) + { + $this->ips = null !== $ips ? (array) $ips : array(); + } + + /** + * Adds a check for the HTTP method. + * + * @param string|string[]|null $method An HTTP method or an array of HTTP methods + */ + public function matchMethod($method) + { + $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : array(); + } + + /** + * Adds a check for request attribute. + * + * @param string $key The request attribute name + * @param string $regexp A Regexp + */ + public function matchAttribute($key, $regexp) + { + $this->attributes[$key] = $regexp; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + if ($this->schemes && !in_array($request->getScheme(), $this->schemes, true)) { + return false; + } + + if ($this->methods && !in_array($request->getMethod(), $this->methods, true)) { + return false; + } + + foreach ($this->attributes as $key => $pattern) { + if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) { + return false; + } + } + + if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) { + return false; + } + + if (IpUtils::checkIp($request->getClientIp(), $this->ips)) { + return true; + } + + // Note to future implementors: add additional checks above the + // foreach above or else your check might not be run! + return count($this->ips) === 0; + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcherInterface.php b/vendor/symfony/http-foundation/RequestMatcherInterface.php new file mode 100644 index 00000000..066e7e8b --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcherInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcherInterface is an interface for strategies to match a Request. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @param Request $request The request to check for a match + * + * @return bool true if the request matches, false otherwise + */ + public function matches(Request $request); +} diff --git a/vendor/symfony/http-foundation/RequestStack.php b/vendor/symfony/http-foundation/RequestStack.php new file mode 100644 index 00000000..3d9cfd0c --- /dev/null +++ b/vendor/symfony/http-foundation/RequestStack.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\HttpFoundation; + +/** + * Request stack that controls the lifecycle of requests. + * + * @author Benjamin Eberlei + */ +class RequestStack +{ + /** + * @var Request[] + */ + private $requests = array(); + + /** + * Pushes a Request on the stack. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + */ + public function push(Request $request) + { + $this->requests[] = $request; + } + + /** + * Pops the current request from the stack. + * + * This operation lets the current request go out of scope. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + * + * @return Request|null + */ + public function pop() + { + if (!$this->requests) { + return; + } + + return array_pop($this->requests); + } + + /** + * @return Request|null + */ + public function getCurrentRequest() + { + return end($this->requests) ?: null; + } + + /** + * Gets the master Request. + * + * Be warned that making your code aware of the master request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * @return Request|null + */ + public function getMasterRequest() + { + if (!$this->requests) { + return; + } + + return $this->requests[0]; + } + + /** + * Returns the parent request of the current. + * + * Be warned that making your code aware of the parent request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * If current Request is the master request, it returns null. + * + * @return Request|null + */ + public function getParentRequest() + { + $pos = count($this->requests) - 2; + + if (!isset($this->requests[$pos])) { + return; + } + + return $this->requests[$pos]; + } +} diff --git a/vendor/symfony/http-foundation/Response.php b/vendor/symfony/http-foundation/Response.php new file mode 100644 index 00000000..4af1e0ba --- /dev/null +++ b/vendor/symfony/http-foundation/Response.php @@ -0,0 +1,1288 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response. + * + * @author Fabien Potencier + */ +class Response +{ + const HTTP_CONTINUE = 100; + const HTTP_SWITCHING_PROTOCOLS = 101; + const HTTP_PROCESSING = 102; // RFC2518 + const HTTP_OK = 200; + const HTTP_CREATED = 201; + const HTTP_ACCEPTED = 202; + const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + const HTTP_NO_CONTENT = 204; + const HTTP_RESET_CONTENT = 205; + const HTTP_PARTIAL_CONTENT = 206; + const HTTP_MULTI_STATUS = 207; // RFC4918 + const HTTP_ALREADY_REPORTED = 208; // RFC5842 + const HTTP_IM_USED = 226; // RFC3229 + const HTTP_MULTIPLE_CHOICES = 300; + const HTTP_MOVED_PERMANENTLY = 301; + const HTTP_FOUND = 302; + const HTTP_SEE_OTHER = 303; + const HTTP_NOT_MODIFIED = 304; + const HTTP_USE_PROXY = 305; + const HTTP_RESERVED = 306; + const HTTP_TEMPORARY_REDIRECT = 307; + const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + const HTTP_BAD_REQUEST = 400; + const HTTP_UNAUTHORIZED = 401; + const HTTP_PAYMENT_REQUIRED = 402; + const HTTP_FORBIDDEN = 403; + const HTTP_NOT_FOUND = 404; + const HTTP_METHOD_NOT_ALLOWED = 405; + const HTTP_NOT_ACCEPTABLE = 406; + const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + const HTTP_REQUEST_TIMEOUT = 408; + const HTTP_CONFLICT = 409; + const HTTP_GONE = 410; + const HTTP_LENGTH_REQUIRED = 411; + const HTTP_PRECONDITION_FAILED = 412; + const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + const HTTP_REQUEST_URI_TOO_LONG = 414; + const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + const HTTP_EXPECTATION_FAILED = 417; + const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 + const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + const HTTP_LOCKED = 423; // RFC4918 + const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817 + const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; + const HTTP_INTERNAL_SERVER_ERROR = 500; + const HTTP_NOT_IMPLEMENTED = 501; + const HTTP_BAD_GATEWAY = 502; + const HTTP_SERVICE_UNAVAILABLE = 503; + const HTTP_GATEWAY_TIMEOUT = 504; + const HTTP_VERSION_NOT_SUPPORTED = 505; + const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + const HTTP_LOOP_DETECTED = 508; // RFC5842 + const HTTP_NOT_EXTENDED = 510; // RFC2774 + const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + + /** + * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var string + */ + protected $version; + + /** + * @var int + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var string + */ + protected $charset; + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2016-03-01). + * + * Unless otherwise noted, the status code is defined in RFC2616. + * + * @var array + */ + public static $statusTexts = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 421 => 'Misdirected Request', // RFC7540 + 422 => 'Unprocessable Entity', // RFC4918 + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 451 => 'Unavailable For Legal Reasons', // RFC7725 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ); + + /** + * Constructor. + * + * @param mixed $content The response content, see setContent() + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + public function __construct($content = '', $status = 200, $headers = array()) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + } + + /** + * Factory method for chainability. + * + * Example: + * + * return Response::create($body, 200) + * ->setSharedMaxAge(300); + * + * @param mixed $content The response content, see setContent() + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($content = '', $status = 200, $headers = array()) + { + return new static($content, $status, $headers); + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string The Response as an HTTP string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Clones the current Response instance. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * Prepares the Response before it is sent to the client. + * + * This method tweaks the Response to ensure that it is + * compliant with RFC 2616. Most of the changes are based on + * the Request that is "associated" with this Response. + * + * @param Request $request A Request instance + * + * @return $this + */ + public function prepare(Request $request) + { + $headers = $this->headers; + + if ($this->isInformational() || $this->isEmpty()) { + $this->setContent(null); + $headers->remove('Content-Type'); + $headers->remove('Content-Length'); + } else { + // Content-type based on the Request + if (!$headers->has('Content-Type')) { + $format = $request->getRequestFormat(); + if (null !== $format && $mimeType = $request->getMimeType($format)) { + $headers->set('Content-Type', $mimeType); + } + } + + // Fix Content-Type + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset='.$charset); + } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) { + // add the charset + $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); + } + + // Fix Content-Length + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + + if ($request->isMethod('HEAD')) { + // cf. RFC2616 14.13 + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + } + + // Fix protocol + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + // Check if we need to send extra expire info headers + if ('1.0' == $this->getProtocolVersion() && false !== strpos($this->headers->get('Cache-Control'), 'no-cache')) { + $this->headers->set('pragma', 'no-cache'); + $this->headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return $this + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + + // headers + foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) { + foreach ($values as $value) { + header($name.': '.$value, false, $this->statusCode); + } + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + + // cookies + foreach ($this->headers->getCookies() as $cookie) { + if ($cookie->isRaw()) { + setrawcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } else { + setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + } + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return $this + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return $this + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif ('cli' !== PHP_SAPI) { + static::closeOutputBuffers(0, true); + } + + return $this; + } + + /** + * Sets the response content. + * + * Valid types are strings, numbers, null, and objects that implement a __toString() method. + * + * @param mixed $content Content that can be cast to string + * + * @return $this + * + * @throws \UnexpectedValueException + */ + public function setContent($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) { + throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * Gets the current response content. + * + * @return string Content + */ + public function getContent() + { + return $this->content; + } + + /** + * Sets the HTTP protocol version (1.0 or 1.1). + * + * @param string $version The HTTP protocol version + * + * @return $this + * + * @final since version 3.2 + */ + public function setProtocolVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @return string The HTTP protocol version + * + * @final since version 3.2 + */ + public function getProtocolVersion() + { + return $this->version; + } + + /** + * Sets the response status code. + * + * @param int $code HTTP status code + * @param mixed $text HTTP status text + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @return $this + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @final since version 3.2 + */ + public function setStatusCode($code, $text = null) + { + $this->statusCode = $code = (int) $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status'; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + /** + * Retrieves the status code for the current web response. + * + * @return int Status code + * + * @final since version 3.2 + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @param string $charset Character set + * + * @return $this + * + * @final since version 3.2 + */ + public function setCharset($charset) + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @return string Character set + * + * @final since version 3.2 + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Returns true if the response is worth caching under any circumstance. + * + * Responses marked "private" with an explicit Cache-Control directive are + * considered uncacheable. + * + * Responses with neither a freshness lifetime (Expires, max-age) nor cache + * validator (Last-Modified, ETag) are considered uncacheable. + * + * @return bool true if the response is worth caching, false otherwise + * + * @final since version 3.3 + */ + public function isCacheable() + { + if (!in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) { + return false; + } + + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + + return $this->isValidateable() || $this->isFresh(); + } + + /** + * Returns true if the response is "fresh". + * + * Fresh responses may be served from cache without any interaction with the + * origin. A response is considered fresh when it includes a Cache-Control/max-age + * indicator or Expires header and the calculated age is less than the freshness lifetime. + * + * @return bool true if the response is fresh, false otherwise + * + * @final since version 3.3 + */ + public function isFresh() + { + return $this->getTtl() > 0; + } + + /** + * Returns true if the response includes headers that can be used to validate + * the response with the origin server using a conditional GET request. + * + * @return bool true if the response is validateable, false otherwise + * + * @final since version 3.3 + */ + public function isValidateable() + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + + /** + * Marks the response as "private". + * + * It makes the response ineligible for serving other clients. + * + * @return $this + * + * @final since version 3.2 + */ + public function setPrivate() + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "public". + * + * It makes the response eligible for serving other clients. + * + * @return $this + * + * @final since version 3.2 + */ + public function setPublic() + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + + return $this; + } + + /** + * Returns true if the response must be revalidated by caches. + * + * This method indicates that the response must not be served stale by a + * cache in any circumstance without first revalidating with the origin. + * When present, the TTL of the response should not be overridden to be + * greater than the value provided by the origin. + * + * @return bool true if the response must be revalidated by a cache, false otherwise + * + * @final since version 3.3 + */ + public function mustRevalidate() + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); + } + + /** + * Returns the Date header as a DateTime instance. + * + * @return \DateTime A \DateTime instance + * + * @throws \RuntimeException When the header is not parseable + * + * @final since version 3.2 + */ + public function getDate() + { + /* + RFC2616 - 14.18 says all Responses need to have a Date. + Make sure we provide one even if it the header + has been removed in the meantime. + */ + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + + return $this->headers->getDate('Date'); + } + + /** + * Sets the Date header. + * + * @param \DateTime $date A \DateTime instance + * + * @return $this + * + * @final since version 3.2 + */ + public function setDate(\DateTime $date) + { + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the age of the response. + * + * @return int The age of the response in seconds + * + * @final since version 3.2 + */ + public function getAge() + { + if (null !== $age = $this->headers->get('Age')) { + return (int) $age; + } + + return max(time() - $this->getDate()->format('U'), 0); + } + + /** + * Marks the response stale by setting the Age header to be equal to the maximum age of the response. + * + * @return $this + */ + public function expire() + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + } + + return $this; + } + + /** + * Returns the value of the Expires header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + * + * @final since version 3.2 + */ + public function getExpires() + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException $e) { + // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past + return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000'); + } + } + + /** + * Sets the Expires HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return $this + * + * @final since version 3.2 + */ + public function setExpires(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Expires'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the number of seconds after the time specified in the response's Date + * header when the response should no longer be considered fresh. + * + * First, it checks for a s-maxage directive, then a max-age directive, and then it falls + * back on an expires header. It returns null when no maximum age can be established. + * + * @return int|null Number of seconds + * + * @final since version 3.2 + */ + public function getMaxAge() + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + + if (null !== $this->getExpires()) { + return $this->getExpires()->format('U') - $this->getDate()->format('U'); + } + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh. + * + * This methods sets the Cache-Control max-age directive. + * + * @param int $value Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setMaxAge($value) + { + $this->headers->addCacheControlDirective('max-age', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. + * + * This methods sets the Cache-Control s-maxage directive. + * + * @param int $value Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setSharedMaxAge($value) + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + + return $this; + } + + /** + * Returns the response's time-to-live in seconds. + * + * It returns null when no freshness information is present in the response. + * + * When the responses TTL is <= 0, the response may not be served from cache without first + * revalidating with the origin. + * + * @return int|null The TTL in seconds + * + * @final since version 3.2 + */ + public function getTtl() + { + if (null !== $maxAge = $this->getMaxAge()) { + return $maxAge - $this->getAge(); + } + } + + /** + * Sets the response's time-to-live for shared caches. + * + * This method adjusts the Cache-Control/s-maxage directive. + * + * @param int $seconds Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setTtl($seconds) + { + $this->setSharedMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Sets the response's time-to-live for private/client caches. + * + * This method adjusts the Cache-Control/max-age directive. + * + * @param int $seconds Number of seconds + * + * @return $this + * + * @final since version 3.2 + */ + public function setClientTtl($seconds) + { + $this->setMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Returns the Last-Modified HTTP header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + * + * @final since version 3.2 + */ + public function getLastModified() + { + return $this->headers->getDate('Last-Modified'); + } + + /** + * Sets the Last-Modified HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return $this + * + * @final since version 3.2 + */ + public function setLastModified(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the literal value of the ETag HTTP header. + * + * @return string|null The ETag HTTP header or null if it does not exist + * + * @final since version 3.2 + */ + public function getEtag() + { + return $this->headers->get('ETag'); + } + + /** + * Sets the ETag value. + * + * @param string|null $etag The ETag unique identifier or null to remove the header + * @param bool $weak Whether you want a weak ETag or not + * + * @return $this + * + * @final since version 3.2 + */ + public function setEtag($etag = null, $weak = false) + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (0 !== strpos($etag, '"')) { + $etag = '"'.$etag.'"'; + } + + $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); + } + + return $this; + } + + /** + * Sets the response's cache headers (validation and/or expiration). + * + * Available options are: etag, last_modified, max_age, s_maxage, private, and public. + * + * @param array $options An array of cache options + * + * @return $this + * + * @throws \InvalidArgumentException + * + * @final since version 3.3 + */ + public function setCache(array $options) + { + if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff)))); + } + + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + + return $this; + } + + /** + * Modifies the response so that it conforms to the rules defined for a 304 status code. + * + * This sets the status, removes the body, and discards any headers + * that MUST NOT be included in 304 responses. + * + * @return $this + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3.5 + * + * @final since version 3.3 + */ + public function setNotModified() + { + $this->setStatusCode(304); + $this->setContent(null); + + // remove headers that MUST NOT be included with 304 Not Modified responses + foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) { + $this->headers->remove($header); + } + + return $this; + } + + /** + * Returns true if the response includes a Vary header. + * + * @return bool true if the response includes a Vary header, false otherwise + * + * @final since version 3.2 + */ + public function hasVary() + { + return null !== $this->headers->get('Vary'); + } + + /** + * Returns an array of header names given in the Vary header. + * + * @return array An array of Vary names + * + * @final since version 3.2 + */ + public function getVary() + { + if (!$vary = $this->headers->get('Vary', null, false)) { + return array(); + } + + $ret = array(); + foreach ($vary as $item) { + $ret = array_merge($ret, preg_split('/[\s,]+/', $item)); + } + + return $ret; + } + + /** + * Sets the Vary header. + * + * @param string|array $headers + * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return $this + * + * @final since version 3.2 + */ + public function setVary($headers, $replace = true) + { + $this->headers->set('Vary', $headers, $replace); + + return $this; + } + + /** + * Determines if the Response validators (ETag, Last-Modified) match + * a conditional value specified in the Request. + * + * If the Response is not modified, it sets the status code to 304 and + * removes the actual content by calling the setNotModified() method. + * + * @param Request $request A Request instance + * + * @return bool true if the Response validators match the Request, false otherwise + * + * @final since version 3.3 + */ + public function isNotModified(Request $request) + { + if (!$request->isMethodCacheable()) { + return false; + } + + $notModified = false; + $lastModified = $this->headers->get('Last-Modified'); + $modifiedSince = $request->headers->get('If-Modified-Since'); + + if ($etags = $request->getETags()) { + $notModified = in_array($this->getEtag(), $etags) || in_array('*', $etags); + } + + if ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified); + } + + if ($notModified) { + $this->setNotModified(); + } + + return $notModified; + } + + /** + * Is response invalid? + * + * @return bool + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * + * @final since version 3.2 + */ + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @return bool + * + * @final since version 3.3 + */ + public function isInformational() + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @return bool + * + * @final since version 3.2 + */ + public function isSuccessful() + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @return bool + * + * @final since version 3.2 + */ + public function isRedirection() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @return bool + * + * @final since version 3.2 + */ + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @return bool + * + * @final since version 3.3 + */ + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @return bool + * + * @final since version 3.2 + */ + public function isOk() + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @return bool + * + * @final since version 3.2 + */ + public function isForbidden() + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @return bool + * + * @final since version 3.2 + */ + public function isNotFound() + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @param string $location + * + * @return bool + * + * @final since version 3.2 + */ + public function isRedirect($location = null) + { + return in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location')); + } + + /** + * Is the response empty? + * + * @return bool + * + * @final since version 3.2 + */ + public function isEmpty() + { + return in_array($this->statusCode, array(204, 304)); + } + + /** + * Cleans or flushes output buffers up to target level. + * + * Resulting level can be greater than target level if a non-removable buffer has been encountered. + * + * @param int $targetLevel The target output buffering level + * @param bool $flush Whether to flush or clean the buffers + * + * @final since version 3.3 + */ + public static function closeOutputBuffers($targetLevel, $flush) + { + $status = ob_get_status(true); + $level = count($status); + // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3 + $flags = defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1; + + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || $flags === ($s['flags'] & $flags) : $s['del'])) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + + /** + * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. + * + * @see http://support.microsoft.com/kb/323308 + * + * @final since version 3.3 + */ + protected function ensureIEOverSSLCompatibility(Request $request) + { + if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) == 1 && true === $request->isSecure()) { + if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} diff --git a/vendor/symfony/http-foundation/ResponseHeaderBag.php b/vendor/symfony/http-foundation/ResponseHeaderBag.php new file mode 100644 index 00000000..df2931be --- /dev/null +++ b/vendor/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,341 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ResponseHeaderBag is a container for Response HTTP headers. + * + * @author Fabien Potencier + */ +class ResponseHeaderBag extends HeaderBag +{ + const COOKIES_FLAT = 'flat'; + const COOKIES_ARRAY = 'array'; + + const DISPOSITION_ATTACHMENT = 'attachment'; + const DISPOSITION_INLINE = 'inline'; + + /** + * @var array + */ + protected $computedCacheControl = array(); + + /** + * @var array + */ + protected $cookies = array(); + + /** + * @var array + */ + protected $headerNames = array(); + + /** + * Constructor. + * + * @param array $headers An array of HTTP headers + */ + public function __construct(array $headers = array()) + { + parent::__construct($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + + /** + * Returns the headers, with original capitalizations. + * + * @return array An array of headers + */ + public function allPreserveCase() + { + $headers = array(); + foreach ($this->all() as $name => $value) { + $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value; + } + + return $headers; + } + + public function allPreserveCaseWithoutCookies() + { + $headers = $this->allPreserveCase(); + if (isset($this->headerNames['set-cookie'])) { + unset($headers[$this->headerNames['set-cookie']]); + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function replace(array $headers = array()) + { + $this->headerNames = array(); + + parent::replace($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + + /** + * {@inheritdoc} + */ + public function all() + { + $headers = parent::all(); + foreach ($this->getCookies() as $cookie) { + $headers['set-cookie'][] = (string) $cookie; + } + + return $headers; + } + + /** + * {@inheritdoc} + */ + public function set($key, $values, $replace = true) + { + $uniqueKey = str_replace('_', '-', strtolower($key)); + + if ('set-cookie' === $uniqueKey) { + if ($replace) { + $this->cookies = array(); + } + foreach ((array) $values as $cookie) { + $this->setCookie(Cookie::fromString($cookie)); + } + $this->headerNames[$uniqueKey] = $key; + + return; + } + + $this->headerNames[$uniqueKey] = $key; + + parent::set($key, $values, $replace); + + // ensure the cache-control header has sensible defaults + if (in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) { + $computed = $this->computeCacheControlValue(); + $this->headers['cache-control'] = array($computed); + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + $uniqueKey = str_replace('_', '-', strtolower($key)); + unset($this->headerNames[$uniqueKey]); + + if ('set-cookie' === $uniqueKey) { + $this->cookies = array(); + + return; + } + + parent::remove($key); + + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = array(); + } + } + + /** + * {@inheritdoc} + */ + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl); + } + + /** + * {@inheritdoc} + */ + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null; + } + + /** + * Sets a cookie. + * + * @param Cookie $cookie + */ + public function setCookie(Cookie $cookie) + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + $this->headerNames['set-cookie'] = 'Set-Cookie'; + } + + /** + * Removes a cookie from the array, but does not unset it in the browser. + * + * @param string $name + * @param string $path + * @param string $domain + */ + public function removeCookie($name, $path = '/', $domain = null) + { + if (null === $path) { + $path = '/'; + } + + unset($this->cookies[$domain][$path][$name]); + + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + + if (empty($this->cookies)) { + unset($this->headerNames['set-cookie']); + } + } + + /** + * Returns an array with all cookies. + * + * @param string $format + * + * @return array + * + * @throws \InvalidArgumentException When the $format is invalid + */ + public function getCookies($format = self::COOKIES_FLAT) + { + if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY)))); + } + + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + + $flattenedCookies = array(); + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Clears a cookie in the browser. + * + * @param string $name + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + */ + public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true) + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly)); + } + + /** + * Generates a HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @return string A string suitable for use as a Content-Disposition field-value + * + * @throws \InvalidArgumentException + * + * @see RFC 6266 + */ + public function makeDisposition($disposition, $filename, $filenameFallback = '') + { + if (!in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' == $filenameFallback) { + $filenameFallback = $filename; + } + + // filenameFallback is not ASCII. + if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + + // percent characters aren't safe in fallback. + if (false !== strpos($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + + // path separators aren't allowed in either. + if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback)); + + if ($filename !== $filenameFallback) { + $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename)); + } + + return $output; + } + + /** + * Returns the calculated value of the cache-control header. + * + * This considers several other headers and calculates or modifies the + * cache-control header to a sensible, conservative value. + * + * @return string + */ + protected function computeCacheControlValue() + { + if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) { + return 'no-cache, private'; + } + + if (!$this->cacheControl) { + // conservative by default + return 'private, must-revalidate'; + } + + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + + // public if s-maxage is defined, private otherwise + if (!isset($this->cacheControl['s-maxage'])) { + return $header.', private'; + } + + return $header; + } +} diff --git a/vendor/symfony/http-foundation/ServerBag.php b/vendor/symfony/http-foundation/ServerBag.php new file mode 100644 index 00000000..0d38c08a --- /dev/null +++ b/vendor/symfony/http-foundation/ServerBag.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ServerBag is a container for HTTP headers from the $_SERVER variable. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Robert Kiss + */ +class ServerBag extends ParameterBag +{ + /** + * Gets the HTTP headers. + * + * @return array + */ + public function getHeaders() + { + $headers = array(); + $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true); + foreach ($this->parameters as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } + // CONTENT_* are not prefixed with HTTP_ + elseif (isset($contentHeaders[$key])) { + $headers[$key] = $value; + } + } + + if (isset($this->parameters['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : ''; + } else { + /* + * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default + * For this workaround to work, add these lines to your .htaccess file: + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * + * A sample .htaccess file: + * RewriteEngine On + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * RewriteCond %{REQUEST_FILENAME} !-f + * RewriteRule ^(.*)$ app.php [QSA,L] + */ + + $authorizationHeader = null; + if (isset($this->parameters['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; + } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; + } + + if (null !== $authorizationHeader) { + if (0 === stripos($authorizationHeader, 'basic ')) { + // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2); + if (count($exploded) == 2) { + list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; + } + } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) { + // In some circumstances PHP_AUTH_DIGEST needs to be set + $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; + $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; + } elseif (0 === stripos($authorizationHeader, 'bearer ')) { + /* + * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables, + * I'll just set $headers['AUTHORIZATION'] here. + * http://php.net/manual/en/reserved.variables.server.php + */ + $headers['AUTHORIZATION'] = $authorizationHeader; + } + } + } + + if (isset($headers['AUTHORIZATION'])) { + return $headers; + } + + // PHP_AUTH_USER/PHP_AUTH_PW + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']); + } elseif (isset($headers['PHP_AUTH_DIGEST'])) { + $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; + } + + return $headers; + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php new file mode 100644 index 00000000..af292e37 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class relates to session attribute storage. + */ +class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + private $name = 'attributes'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $attributes = array(); + + /** + * Constructor. + * + * @param string $storageKey The key used to store attributes in the session + */ + public function __construct($storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$attributes) + { + $this->attributes = &$attributes; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->attributes = array(); + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + if (array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $return = $this->attributes; + $this->attributes = array(); + + return $return; + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->attributes); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return count($this->attributes); + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 00000000..0d8d1799 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Attributes store. + * + * @author Drak + */ +interface AttributeBagInterface extends SessionBagInterface +{ + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function has($name); + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get($name, $default = null); + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value); + + /** + * Returns attributes. + * + * @return array Attributes + */ + public function all(); + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value or null when it does not exist + */ + public function remove($name); +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php new file mode 100644 index 00000000..d797a6f2 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class provides structured storage of session attributes using + * a name spacing character in the key. + * + * @author Drak + */ +class NamespacedAttributeBag extends AttributeBag +{ + /** + * Namespace character. + * + * @var string + */ + private $namespaceCharacter; + + /** + * Constructor. + * + * @param string $storageKey Session storage key + * @param string $namespaceCharacter Namespace character to use in keys + */ + public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/') + { + $this->namespaceCharacter = $namespaceCharacter; + parent::__construct($storageKey); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return false; + } + + return array_key_exists($name, $attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return $default; + } + + return array_key_exists($name, $attributes) ? $attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $attributes = &$this->resolveAttributePath($name, true); + $name = $this->resolveKey($name); + $attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + $attributes = &$this->resolveAttributePath($name); + $name = $this->resolveKey($name); + if (null !== $attributes && array_key_exists($name, $attributes)) { + $retval = $attributes[$name]; + unset($attributes[$name]); + } + + return $retval; + } + + /** + * Resolves a path in attributes property and returns it as a reference. + * + * This method allows structured namespacing of session attributes. + * + * @param string $name Key name + * @param bool $writeContext Write context, default false + * + * @return array + */ + protected function &resolveAttributePath($name, $writeContext = false) + { + $array = &$this->attributes; + $name = (strpos($name, $this->namespaceCharacter) === 0) ? substr($name, 1) : $name; + + // Check if there is anything to do, else return + if (!$name) { + return $array; + } + + $parts = explode($this->namespaceCharacter, $name); + if (count($parts) < 2) { + if (!$writeContext) { + return $array; + } + + $array[$parts[0]] = array(); + + return $array; + } + + unset($parts[count($parts) - 1]); + + foreach ($parts as $part) { + if (null !== $array && !array_key_exists($part, $array)) { + $array[$part] = $writeContext ? array() : null; + } + + $array = &$array[$part]; + } + + return $array; + } + + /** + * Resolves the key from the name. + * + * This is the last part in a dot separated string. + * + * @param string $name + * + * @return string + */ + protected function resolveKey($name) + { + if (false !== $pos = strrpos($name, $this->namespaceCharacter)) { + $name = substr($name, $pos + 1); + } + + return $name; + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 00000000..ddd603fd --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + + /** + * Flash messages. + * + * @var array + */ + private $flashes = array('display' => array(), 'new' => array()); + + /** + * The storage key for flashes in the session. + * + * @var string + */ + private $storageKey; + + /** + * Constructor. + * + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array(); + $this->flashes['new'] = array(); + } + + /** + * {@inheritdoc} + */ + public function add($type, $message) + { + $this->flashes['new'][$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek($type, array $default = array()) + { + return $this->has($type) ? $this->flashes['display'][$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : array(); + } + + /** + * {@inheritdoc} + */ + public function get($type, array $default = array()) + { + $return = $default; + + if (!$this->has($type)) { + return $return; + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->flashes['display']; + $this->flashes = array('new' => array(), 'display' => array()); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes['new'] = $messages; + } + + /** + * {@inheritdoc} + */ + public function set($type, $messages) + { + $this->flashes['new'][$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php new file mode 100644 index 00000000..85b4f00b --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + + /** + * Flash messages. + * + * @var array + */ + private $flashes = array(); + + /** + * The storage key for flashes in the session. + * + * @var string + */ + private $storageKey; + + /** + * Constructor. + * + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + } + + /** + * {@inheritdoc} + */ + public function add($type, $message) + { + $this->flashes[$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek($type, array $default = array()) + { + return $this->has($type) ? $this->flashes[$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function get($type, array $default = array()) + { + if (!$this->has($type)) { + return $default; + } + + $return = $this->flashes[$type]; + + unset($this->flashes[$type]); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->peekAll(); + $this->flashes = array(); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set($type, $messages) + { + $this->flashes[$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes = $messages; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes) && $this->flashes[$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 00000000..25f3d57b --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface extends SessionBagInterface +{ + /** + * Adds a flash message for type. + * + * @param string $type + * @param string $message + */ + public function add($type, $message); + + /** + * Registers a message for a given type. + * + * @param string $type + * @param string|array $message + */ + public function set($type, $message); + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function peek($type, array $default = array()); + + /** + * Gets all flash messages. + * + * @return array + */ + public function peekAll(); + + /** + * Gets and clears flash from the stack. + * + * @param string $type + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function get($type, array $default = array()); + + /** + * Gets and clears flashes from the stack. + * + * @return array + */ + public function all(); + + /** + * Sets all flash messages. + * + * @param array $messages + */ + public function setAll(array $messages); + + /** + * Has flash messages for a given type? + * + * @param string $type + * + * @return bool + */ + public function has($type); + + /** + * Returns a list of all defined types. + * + * @return array + */ + public function keys(); +} diff --git a/vendor/symfony/http-foundation/Session/Session.php b/vendor/symfony/http-foundation/Session/Session.php new file mode 100644 index 00000000..70bcf3e0 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Session.php @@ -0,0 +1,261 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; + +/** + * Session. + * + * @author Fabien Potencier + * @author Drak + */ +class Session implements SessionInterface, \IteratorAggregate, \Countable +{ + /** + * Storage driver. + * + * @var SessionStorageInterface + */ + protected $storage; + + /** + * @var string + */ + private $flashName; + + /** + * @var string + */ + private $attributeName; + + /** + * Constructor. + * + * @param SessionStorageInterface $storage A SessionStorageInterface instance + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + */ + public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + $this->storage = $storage ?: new NativeSessionStorage(); + + $attributes = $attributes ?: new AttributeBag(); + $this->attributeName = $attributes->getName(); + $this->registerBag($attributes); + + $flashes = $flashes ?: new FlashBag(); + $this->flashName = $flashes->getName(); + $this->registerBag($flashes); + } + + /** + * {@inheritdoc} + */ + public function start() + { + return $this->storage->start(); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return $this->getAttributeBag()->has($name); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return $this->getAttributeBag()->get($name, $default); + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->getAttributeBag()->set($name, $value); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->getAttributeBag()->all(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->getAttributeBag()->replace($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + return $this->getAttributeBag()->remove($name); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->storage->getBag($this->attributeName)->clear(); + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->storage->isStarted(); + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->getAttributeBag()->all()); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return count($this->getAttributeBag()->all()); + } + + /** + * {@inheritdoc} + */ + public function invalidate($lifetime = null) + { + $this->storage->clear(); + + return $this->migrate(true, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function migrate($destroy = false, $lifetime = null) + { + return $this->storage->regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->storage->save(); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->storage->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + $this->storage->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->storage->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->storage->setName($name); + } + + /** + * {@inheritdoc} + */ + public function getMetadataBag() + { + return $this->storage->getMetadataBag(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->storage->registerBag($bag); + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + return $this->storage->getBag($name); + } + + /** + * Gets the flashbag interface. + * + * @return FlashBagInterface + */ + public function getFlashBag() + { + return $this->getBag($this->flashName); + } + + /** + * Gets the attributebag interface. + * + * Note that this method was added to help with IDE autocompletion. + * + * @return AttributeBagInterface + */ + private function getAttributeBag() + { + return $this->storage->getBag($this->attributeName); + } +} diff --git a/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/vendor/symfony/http-foundation/Session/SessionBagInterface.php new file mode 100644 index 00000000..aca18aac --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionBagInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session Bag store. + * + * @author Drak + */ +interface SessionBagInterface +{ + /** + * Gets this bag's name. + * + * @return string + */ + public function getName(); + + /** + * Initializes the Bag. + * + * @param array $array + */ + public function initialize(array &$array); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + public function getStorageKey(); + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained + */ + public function clear(); +} diff --git a/vendor/symfony/http-foundation/Session/SessionInterface.php b/vendor/symfony/http-foundation/Session/SessionInterface.php new file mode 100644 index 00000000..d3fcd2ee --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionInterface.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Interface for the session. + * + * @author Drak + */ +interface SessionInterface +{ + /** + * Starts the session storage. + * + * @return bool True if session started + * + * @throws \RuntimeException If session fails to start. + */ + public function start(); + + /** + * Returns the session ID. + * + * @return string The session ID + */ + public function getId(); + + /** + * Sets the session ID. + * + * @param string $id + */ + public function setId($id); + + /** + * Returns the session name. + * + * @return mixed The session name + */ + public function getName(); + + /** + * Sets the session name. + * + * @param string $name + */ + public function setName($name); + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session invalidated, false if error + */ + public function invalidate($lifetime = null); + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param bool $destroy Whether to delete the old session or leave it to garbage collection + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session migrated, false if error + */ + public function migrate($destroy = false, $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + public function save(); + + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function has($name); + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get($name, $default = null); + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value); + + /** + * Returns attributes. + * + * @return array Attributes + */ + public function all(); + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value or null when it does not exist + */ + public function remove($name); + + /** + * Clears all attributes. + */ + public function clear(); + + /** + * Checks if the session was started. + * + * @return bool + */ + public function isStarted(); + + /** + * Registers a SessionBagInterface with the session. + * + * @param SessionBagInterface $bag + */ + public function registerBag(SessionBagInterface $bag); + + /** + * Gets a bag instance by name. + * + * @param string $name + * + * @return SessionBagInterface + */ + public function getBag($name); + + /** + * Gets session meta. + * + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php new file mode 100644 index 00000000..962a3878 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.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\HttpFoundation\Session\Storage\Handler; + +/** + * MemcacheSessionHandler. + * + * @author Drak + */ +class MemcacheSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Memcache Memcache driver + */ + private $memcache; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcache keys in order to avoid collision + * * expiretime: The time to live in seconds + * + * @param \Memcache $memcache A \Memcache instance + * @param array $options An associative array of Memcache options + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcache $memcache, array $options = array()) + { + if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) { + throw new \InvalidArgumentException(sprintf( + 'The following options are not supported "%s"', implode(', ', $diff) + )); + } + + $this->memcache = $memcache; + $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return $this->memcache->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return $this->memcache->delete($this->prefix.$sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // not required here because memcache will auto expire the records anyhow. + return true; + } + + /** + * Return a Memcache instance. + * + * @return \Memcache + */ + protected function getMemcache() + { + return $this->memcache; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php new file mode 100644 index 00000000..76b08e2d --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * MemcachedSessionHandler. + * + * Memcached based session storage handler based on the Memcached class + * provided by the PHP memcached extension. + * + * @see http://php.net/memcached + * + * @author Drak + */ +class MemcachedSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Memcached Memcached driver + */ + private $memcached; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcached keys in order to avoid collision + * * expiretime: The time to live in seconds + * + * @param \Memcached $memcached A \Memcached instance + * @param array $options An associative array of Memcached options + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcached $memcached, array $options = array()) + { + $this->memcached = $memcached; + + if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) { + throw new \InvalidArgumentException(sprintf( + 'The following options are not supported "%s"', implode(', ', $diff) + )); + } + + $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return $this->memcached->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return $this->memcached->delete($this->prefix.$sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // not required here because memcached will auto expire the records anyhow. + return true; + } + + /** + * Return a Memcached instance. + * + * @return \Memcached + */ + protected function getMemcached() + { + return $this->memcached; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 00000000..8408f000 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * MongoDB session handler. + * + * @author Markus Bachmann + */ +class MongoDbSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Mongo|\MongoClient|\MongoDB\Client + */ + private $mongo; + + /** + * @var \MongoCollection + */ + private $collection; + + /** + * @var array + */ + private $options; + + /** + * Constructor. + * + * List of available options: + * * database: The name of the database [required] + * * collection: The name of the collection [required] + * * id_field: The field name for storing the session id [default: _id] + * * data_field: The field name for storing the session data [default: data] + * * time_field: The field name for storing the timestamp [default: time] + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at] + * + * It is strongly recommended to put an index on the `expiry_field` for + * garbage-collection. Alternatively it's possible to automatically expire + * the sessions in the database as described below: + * + * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions + * automatically. Such an index can for example look like this: + * + * db..ensureIndex( + * { "": 1 }, + * { "expireAfterSeconds": 0 } + * ) + * + * More details on: http://docs.mongodb.org/manual/tutorial/expire-data/ + * + * If you use such an index, you can drop `gc_probability` to 0 since + * no garbage-collection is required. + * + * @param \Mongo|\MongoClient|\MongoDB\Client $mongo A MongoDB\Client, MongoClient or Mongo instance + * @param array $options An associative array of field options + * + * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided + * @throws \InvalidArgumentException When "database" or "collection" not provided + */ + public function __construct($mongo, array $options) + { + if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) { + throw new \InvalidArgumentException('MongoClient or Mongo instance required'); + } + + if (!isset($options['database']) || !isset($options['collection'])) { + throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler'); + } + + $this->mongo = $mongo; + + $this->options = array_merge(array( + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => 'expires_at', + ), $options); + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; + + $this->getCollection()->$methodName(array( + $this->options['id_field'] => $sessionId, + )); + + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; + + $this->getCollection()->$methodName(array( + $this->options['expiry_field'] => array('$lt' => $this->createDateTime()), + )); + + return true; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); + + $fields = array( + $this->options['time_field'] => $this->createDateTime(), + $this->options['expiry_field'] => $expiry, + ); + + $options = array('upsert' => true); + + if ($this->mongo instanceof \MongoDB\Client) { + $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY); + } else { + $fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY); + $options['multiple'] = false; + } + + $methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update'; + + $this->getCollection()->$methodName( + array($this->options['id_field'] => $sessionId), + array('$set' => $fields), + $options + ); + + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $dbData = $this->getCollection()->findOne(array( + $this->options['id_field'] => $sessionId, + $this->options['expiry_field'] => array('$gte' => $this->createDateTime()), + )); + + if (null === $dbData) { + return ''; + } + + if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) { + return $dbData[$this->options['data_field']]->getData(); + } + + return $dbData[$this->options['data_field']]->bin; + } + + /** + * Return a "MongoCollection" instance. + * + * @return \MongoCollection + */ + private function getCollection() + { + if (null === $this->collection) { + $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); + } + + return $this->collection; + } + + /** + * Return a Mongo instance. + * + * @return \Mongo|\MongoClient|\MongoDB\Client + */ + protected function getMongo() + { + return $this->mongo; + } + + /** + * Create a date object using the class appropriate for the current mongo connection. + * + * Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime + * + * @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now. + * + * @return \MongoDate|\MongoDB\BSON\UTCDateTime + */ + private function createDateTime($seconds = null) + { + if (null === $seconds) { + $seconds = time(); + } + + if ($this->mongo instanceof \MongoDB\Client) { + return new \MongoDB\BSON\UTCDateTime($seconds * 1000); + } + + return new \MongoDate($seconds); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php new file mode 100644 index 00000000..1be0a398 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * NativeFileSessionHandler. + * + * Native session handler using PHP's built in file storage. + * + * @author Drak + */ +class NativeFileSessionHandler extends NativeSessionHandler +{ + /** + * Constructor. + * + * @param string $savePath Path of directory to save session files + * Default null will leave setting as defined by PHP. + * '/path', 'N;/path', or 'N;octal-mode;/path + * + * @see http://php.net/session.configuration.php#ini.session.save-path for further details. + * + * @throws \InvalidArgumentException On invalid $savePath + */ + public function __construct($savePath = null) + { + if (null === $savePath) { + $savePath = ini_get('session.save_path'); + } + + $baseDir = $savePath; + + if ($count = substr_count($savePath, ';')) { + if ($count > 2) { + throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath)); + } + + // characters after last ';' are the path + $baseDir = ltrim(strrchr($savePath, ';'), ';'); + } + + if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir)); + } + + ini_set('session.save_path', $savePath); + ini_set('session.save_handler', 'files'); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php new file mode 100644 index 00000000..4ae410f9 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Adds SessionHandler functionality if available. + * + * @see http://php.net/sessionhandler + */ +class NativeSessionHandler extends \SessionHandler +{ +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php new file mode 100644 index 00000000..1516d431 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.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\HttpFoundation\Session\Storage\Handler; + +/** + * NullSessionHandler. + * + * Can be used in unit testing or in a situations where persisted sessions are not desired. + * + * @author Drak + */ +class NullSessionHandler implements \SessionHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return true; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php new file mode 100644 index 00000000..8909a5f4 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -0,0 +1,721 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Session handler using a PDO connection to read and write data. + * + * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements + * different locking strategies to handle concurrent access to the same session. + * Locking is necessary to prevent loss of data due to race conditions and to keep + * the session data consistent between read() and write(). With locking, requests + * for the same session will wait until the other one finished writing. For this + * reason it's best practice to close a session as early as possible to improve + * concurrency. PHPs internal files session handler also implements locking. + * + * Attention: Since SQLite does not support row level locks but locks the whole database, + * it means only one session can be accessed at a time. Even different sessions would wait + * for another to finish. So saving session in SQLite should only be considered for + * development or prototypes. + * + * Session data is a binary string that can contain non-printable characters like the null byte. + * For this reason it must be saved in a binary column in the database like BLOB in MySQL. + * Saving it in a character column could corrupt the data. You can use createTable() + * to initialize a correctly defined table. + * + * @see http://php.net/sessionhandlerinterface + * + * @author Fabien Potencier + * @author Michael Williams + * @author Tobias Schultze + */ +class PdoSessionHandler implements \SessionHandlerInterface +{ + /** + * No locking is done. This means sessions are prone to loss of data due to + * race conditions of concurrent requests to the same session. The last session + * write will win in this case. It might be useful when you implement your own + * logic to deal with this like an optimistic approach. + */ + const LOCK_NONE = 0; + + /** + * Creates an application-level lock on a session. The disadvantage is that the + * lock is not enforced by the database and thus other, unaware parts of the + * application could still concurrently modify the session. The advantage is it + * does not require a transaction. + * This mode is not available for SQLite and not yet implemented for oci and sqlsrv. + */ + const LOCK_ADVISORY = 1; + + /** + * Issues a real row lock. Since it uses a transaction between opening and + * closing a session, you have to be careful when you use same database connection + * that you also use for your application logic. This mode is the default because + * it's the only reliable solution across DBMSs. + */ + const LOCK_TRANSACTIONAL = 2; + + /** + * @var \PDO|null PDO instance or null when not connected yet + */ + private $pdo; + + /** + * @var string|null|false DSN string or null for session.save_path or false when lazy connection disabled + */ + private $dsn = false; + + /** + * @var string Database driver + */ + private $driver; + + /** + * @var string Table name + */ + private $table = 'sessions'; + + /** + * @var string Column for session id + */ + private $idCol = 'sess_id'; + + /** + * @var string Column for session data + */ + private $dataCol = 'sess_data'; + + /** + * @var string Column for lifetime + */ + private $lifetimeCol = 'sess_lifetime'; + + /** + * @var string Column for timestamp + */ + private $timeCol = 'sess_time'; + + /** + * @var string Username when lazy-connect + */ + private $username = ''; + + /** + * @var string Password when lazy-connect + */ + private $password = ''; + + /** + * @var array Connection options when lazy-connect + */ + private $connectionOptions = array(); + + /** + * @var int The strategy for locking, see constants + */ + private $lockMode = self::LOCK_TRANSACTIONAL; + + /** + * It's an array to support multiple reads before closing which is manual, non-standard usage. + * + * @var \PDOStatement[] An array of statements to release advisory locks + */ + private $unlockStatements = array(); + + /** + * @var bool True when the current session exists but expired according to session.gc_maxlifetime + */ + private $sessionExpired = false; + + /** + * @var bool Whether a transaction is active + */ + private $inTransaction = false; + + /** + * @var bool Whether gc() has been called + */ + private $gcCalled = false; + + /** + * Constructor. + * + * You can either pass an existing database connection as PDO instance or + * pass a DSN string that will be used to lazy-connect to the database + * when the session is actually used. Furthermore it's possible to pass null + * which will then use the session.save_path ini setting as PDO DSN parameter. + * + * List of available options: + * * db_table: The name of the table [default: sessions] + * * db_id_col: The column where to store the session id [default: sess_id] + * * db_data_col: The column where to store the session data [default: sess_data] + * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime] + * * db_time_col: The column where to store the timestamp [default: sess_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: array()] + * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] + * + * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or null + * @param array $options An associative array of options + * + * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + */ + public function __construct($pdoOrDsn = null, array $options = array()) + { + if ($pdoOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__)); + } + + $this->pdo = $pdoOrDsn; + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } else { + $this->dsn = $pdoOrDsn; + } + + $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table; + $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol; + $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol; + $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol; + $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol; + $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username; + $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password; + $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions; + $this->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode; + } + + /** + * Creates the table to store sessions which can be called once for setup. + * + * Session ID is saved in a column of maximum length 128 because that is enough even + * for a 512 bit configured session.hash_function like Whirlpool. Session data is + * saved in a BLOB. One could also use a shorter inlined varbinary column + * if one was sure the data fits into it. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable() + { + // connect if we are not yet + $this->getConnection(); + + switch ($this->driver) { + case 'mysql': + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB"; + break; + case 'sqlite': + $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'pgsql': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'oci': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'sqlsrv': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + + try { + $this->pdo->exec($sql); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * Returns true when the current session exists but expired according to session.gc_maxlifetime. + * + * Can be used to distinguish between a new session and one that expired due to inactivity. + * + * @return bool Whether current session expired + */ + public function isSessionExpired() + { + return $this->sessionExpired; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + if (null === $this->pdo) { + $this->connect($this->dsn ?: $savePath); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + try { + return $this->doRead($sessionId); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. + // This way, pruning expired sessions does not block them from being started while the current session is used. + $this->gcCalled = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + // delete the record associated with this id + $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + + try { + // We use a single MERGE SQL query when supported by the database. + $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime); + if (null !== $mergeStmt) { + $mergeStmt->execute(); + + return true; + } + + $updateStmt = $this->pdo->prepare( + "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id" + ); + $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $updateStmt->execute(); + + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in + // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior). + // We can just catch such an error and re-execute the update. This is similar to a serializable + // transaction with retry logic on serialization failures but without the overhead and without possible + // false positives due to longer gap locking. + if (!$updateStmt->rowCount()) { + try { + $insertStmt = $this->pdo->prepare( + "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)" + ); + $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $insertStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $insertStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys + if (0 === strpos($e->getCode(), '23')) { + $updateStmt->execute(); + } else { + throw $e; + } + } + } + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->commit(); + + while ($unlockStmt = array_shift($this->unlockStatements)) { + $unlockStmt->execute(); + } + + if ($this->gcCalled) { + $this->gcCalled = false; + + // delete the session records that have expired + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time"; + + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + } + + if (false !== $this->dsn) { + $this->pdo = null; // only close lazy-connection + } + + return true; + } + + /** + * Lazy-connects to the database. + * + * @param string $dsn DSN string + */ + private function connect($dsn) + { + $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + /** + * Helper method to begin a transaction. + * + * Since SQLite does not support row level locks, we have to acquire a reserved lock + * on the database immediately. Because of https://bugs.php.net/42766 we have to create + * such a transaction manually which also means we cannot use PDO::commit or + * PDO::rollback or PDO::inTransaction for SQLite. + * + * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions + * due to http://www.mysqlperformanceblog.com/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . + * So we change it to READ COMMITTED. + */ + private function beginTransaction() + { + if (!$this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION'); + } else { + if ('mysql' === $this->driver) { + $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); + } + $this->pdo->beginTransaction(); + } + $this->inTransaction = true; + } + } + + /** + * Helper method to commit a transaction. + */ + private function commit() + { + if ($this->inTransaction) { + try { + // commit read-write transaction which also releases the lock + if ('sqlite' === $this->driver) { + $this->pdo->exec('COMMIT'); + } else { + $this->pdo->commit(); + } + $this->inTransaction = false; + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + } + + /** + * Helper method to rollback a transaction. + */ + private function rollback() + { + // We only need to rollback if we are in a transaction. Otherwise the resulting + // error would hide the real problem why rollback was called. We might not be + // in a transaction when not using the transactional locking behavior or when + // two callbacks (e.g. destroy and write) are invoked that both fail. + if ($this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('ROLLBACK'); + } else { + $this->pdo->rollBack(); + } + $this->inTransaction = false; + } + } + + /** + * Reads the session data in respect to the different locking strategies. + * + * We need to make sure we do not return session data that is already considered garbage according + * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. + * + * @param string $sessionId Session ID + * + * @return string The session data + */ + private function doRead($sessionId) + { + $this->sessionExpired = false; + + if (self::LOCK_ADVISORY === $this->lockMode) { + $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); + } + + $selectSql = $this->getSelectSql(); + $selectStmt = $this->pdo->prepare($selectSql); + $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + + do { + $selectStmt->execute(); + $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); + + if ($sessionRows) { + if ($sessionRows[0][1] + $sessionRows[0][2] < time()) { + $this->sessionExpired = true; + + return ''; + } + + return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; + } + + if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block + // until other connections to the session are committed. + try { + $insertStmt = $this->pdo->prepare( + "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)" + ); + $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt->bindValue(':data', '', \PDO::PARAM_LOB); + $insertStmt->bindValue(':lifetime', 0, \PDO::PARAM_INT); + $insertStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Catch duplicate key error because other connection created the session already. + // It would only not be the case when the other connection destroyed the session. + if (0 === strpos($e->getCode(), '23')) { + // Retrieve finished session data written by concurrent connection by restarting the loop. + // We have to start a new transaction as a failed query will mark the current transaction as + // aborted in PostgreSQL and disallow further queries within it. + $this->rollback(); + $this->beginTransaction(); + continue; + } + + throw $e; + } + } + + return ''; + } while (true); + } + + /** + * Executes an application-level lock on the database. + * + * @param string $sessionId Session ID + * + * @return \PDOStatement The statement that needs to be executed later to release the lock + * + * @throws \DomainException When an unsupported PDO driver is used + * + * @todo implement missing advisory locks + * - for oci using DBMS_LOCK.REQUEST + * - for sqlsrv using sp_getapplock with LockOwner = Session + */ + private function doAdvisoryLock($sessionId) + { + switch ($this->driver) { + case 'mysql': + // should we handle the return value? 0 on timeout, null on error + // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout + $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); + $stmt->bindValue(':key', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); + $releaseStmt->bindValue(':key', $sessionId, \PDO::PARAM_STR); + + return $releaseStmt; + case 'pgsql': + // Obtaining an exclusive session level advisory lock requires an integer key. + // So we convert the HEX representation of the session id to an integer. + // Since integers are signed, we have to skip one hex char to fit in the range. + if (4 === PHP_INT_SIZE) { + $sessionInt1 = hexdec(substr($sessionId, 0, 7)); + $sessionInt2 = hexdec(substr($sessionId, 7, 7)); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)'); + $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)'); + $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + } else { + $sessionBigInt = hexdec(substr($sessionId, 0, 15)); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)'); + $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)'); + $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + } + + return $releaseStmt; + case 'sqlite': + throw new \DomainException('SQLite does not support advisory locks.'); + default: + throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + /** + * Return a locking or nonlocking SQL query to read session information. + * + * @return string The SQL string + * + * @throws \DomainException When an unsupported PDO driver is used + */ + private function getSelectSql() + { + if (self::LOCK_TRANSACTIONAL === $this->lockMode) { + $this->beginTransaction(); + + switch ($this->driver) { + case 'mysql': + case 'oci': + case 'pgsql': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE"; + case 'sqlsrv': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id"; + case 'sqlite': + // we already locked when starting transaction + break; + default: + throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id"; + } + + /** + * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. + * + * @param string $sessionId Session ID + * @param string $data Encoded session data + * @param int $maxlifetime session.gc_maxlifetime + * + * @return \PDOStatement|null The merge statement or null when not supported + */ + private function getMergeStatement($sessionId, $data, $maxlifetime) + { + $mergeSql = null; + switch (true) { + case 'mysql' === $this->driver: + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'oci' === $this->driver: + // DUAL is Oracle specific dummy table + $mergeSql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?"; + break; + case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + $mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $this->driver: + $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + break; + case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + } + + if (null !== $mergeSql) { + $mergeStmt = $this->pdo->prepare($mergeSql); + + if ('sqlsrv' === $this->driver || 'oci' === $this->driver) { + $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); + $mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT); + $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB); + $mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT); + } else { + $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); + } + + return $mergeStmt; + } + } + + /** + * Return a PDO instance. + * + * @return \PDO + */ + protected function getConnection() + { + if (null === $this->pdo) { + $this->connect($this->dsn ?: ini_get('session.save_path')); + } + + return $this->pdo; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php new file mode 100644 index 00000000..d49c36ca --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.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\HttpFoundation\Session\Storage\Handler; + +/** + * Wraps another SessionHandlerInterface to only write the session when it has been modified. + * + * @author Adrien Brault + */ +class WriteCheckSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \SessionHandlerInterface + */ + private $wrappedSessionHandler; + + /** + * @var array sessionId => session + */ + private $readSessions; + + public function __construct(\SessionHandlerInterface $wrappedSessionHandler) + { + $this->wrappedSessionHandler = $wrappedSessionHandler; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return $this->wrappedSessionHandler->close(); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return $this->wrappedSessionHandler->destroy($sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return $this->wrappedSessionHandler->gc($maxlifetime); + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return $this->wrappedSessionHandler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $session = $this->wrappedSessionHandler->read($sessionId); + + $this->readSessions[$sessionId] = $session; + + return $session; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + if (isset($this->readSessions[$sessionId]) && $data === $this->readSessions[$sessionId]) { + return true; + } + + return $this->wrappedSessionHandler->write($sessionId, $data); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php new file mode 100644 index 00000000..322dd560 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.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\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Metadata container. + * + * Adds metadata to the session. + * + * @author Drak + */ +class MetadataBag implements SessionBagInterface +{ + const CREATED = 'c'; + const UPDATED = 'u'; + const LIFETIME = 'l'; + + /** + * @var string + */ + private $name = '__metadata'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0); + + /** + * Unix timestamp. + * + * @var int + */ + private $lastUsed; + + /** + * @var int + */ + private $updateThreshold; + + /** + * Constructor. + * + * @param string $storageKey The key used to store bag in the session + * @param int $updateThreshold The time to wait between two UPDATED updates + */ + public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0) + { + $this->storageKey = $storageKey; + $this->updateThreshold = $updateThreshold; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array) + { + $this->meta = &$array; + + if (isset($array[self::CREATED])) { + $this->lastUsed = $this->meta[self::UPDATED]; + + $timeStamp = time(); + if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) { + $this->meta[self::UPDATED] = $timeStamp; + } + } else { + $this->stampCreated(); + } + } + + /** + * Gets the lifetime that the session cookie was set with. + * + * @return int + */ + public function getLifetime() + { + return $this->meta[self::LIFETIME]; + } + + /** + * Stamps a new session's metadata. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function stampNew($lifetime = null) + { + $this->stampCreated($lifetime); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * Gets the created timestamp metadata. + * + * @return int Unix timestamp + */ + public function getCreated() + { + return $this->meta[self::CREATED]; + } + + /** + * Gets the last used metadata. + * + * @return int Unix timestamp + */ + public function getLastUsed() + { + return $this->lastUsed; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // nothing to do + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * Sets name. + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + private function stampCreated($lifetime = null) + { + $timeStamp = time(); + $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; + $this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 00000000..348fd230 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,268 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * MockArraySessionStorage mocks the session for unit tests. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * When doing functional testing, you should use MockFileSessionStorage instead. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Drak + */ +class MockArraySessionStorage implements SessionStorageInterface +{ + /** + * @var string + */ + protected $id = ''; + + /** + * @var string + */ + protected $name; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var array + */ + protected $data = array(); + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var array|SessionBagInterface[] + */ + protected $bags = array(); + + /** + * Constructor. + * + * @param string $name Session name + * @param MetadataBag $metaBag MetadataBag instance + */ + public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->name = $name; + $this->setMetadataBag($metaBag); + } + + /** + * Sets the session data. + * + * @param array $array + */ + public function setSessionData(array $array) + { + $this->data = $array; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (empty($this->id)) { + $this->id = $this->generateId(); + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + $this->metadataBag->stampNew($lifetime); + $this->id = $this->generateId(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + if ($this->started) { + throw new \LogicException('Cannot set session ID after the session has started.'); + } + + $this->id = $id; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started || $this->closed) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed'); + } + // nothing to do since we don't persist the session data + $this->closed = false; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $this->data = array(); + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets the MetadataBag. + * + * @param MetadataBag $bag + */ + public function setMetadataBag(MetadataBag $bag = null) + { + if (null === $bag) { + $bag = new MetadataBag(); + } + + $this->metadataBag = $bag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * Generates a session ID. + * + * This doesn't need to be particularly cryptographically secure since this is just + * a mock. + * + * @return string + */ + protected function generateId() + { + return hash('sha256', uniqid('ss_mock_', true)); + } + + protected function loadSession() + { + $bags = array_merge($this->bags, array($this->metadataBag)); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array(); + $bag->initialize($this->data[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 00000000..71f9e555 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * MockFileSessionStorage is used to mock sessions for + * functional testing when done in a single PHP process. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle and this class does + * not pollute any session related globals, including session_*() functions + * or session.* PHP ini directives. + * + * @author Drak + */ +class MockFileSessionStorage extends MockArraySessionStorage +{ + /** + * @var string + */ + private $savePath; + + /** + * Constructor. + * + * @param string $savePath Path of directory to save session files + * @param string $name Session name + * @param MetadataBag $metaBag MetadataBag instance + */ + public function __construct($savePath = null, $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + if (null === $savePath) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $savePath)); + } + + $this->savePath = $savePath; + + parent::__construct($name, $metaBag); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (!$this->id) { + $this->id = $this->generateId(); + } + + $this->read(); + + $this->started = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + if ($destroy) { + $this->destroy(); + } + + return parent::regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed'); + } + + file_put_contents($this->getFilePath(), serialize($this->data)); + + // this is needed for Silex, where the session object is re-used across requests + // in functional tests. In Symfony, the container is rebooted, so we don't have + // this issue + $this->started = false; + } + + /** + * Deletes a session from persistent storage. + * Deliberately leaves session data in memory intact. + */ + private function destroy() + { + if (is_file($this->getFilePath())) { + unlink($this->getFilePath()); + } + } + + /** + * Calculate path to file. + * + * @return string File path + */ + private function getFilePath() + { + return $this->savePath.'/'.$this->id.'.mocksess'; + } + + /** + * Reads session from storage and loads session. + */ + private function read() + { + $filePath = $this->getFilePath(); + $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array(); + + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php new file mode 100644 index 00000000..0cc818ea --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,418 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\Debug\Exception\ContextErrorException; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * This provides a base class for session attribute storage. + * + * @author Drak + */ +class NativeSessionStorage implements SessionStorageInterface +{ + /** + * Array of SessionBagInterface. + * + * @var SessionBagInterface[] + */ + protected $bags; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var AbstractProxy + */ + protected $saveHandler; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * Constructor. + * + * Depending on how you want the storage driver to behave you probably + * want to override this constructor entirely. + * + * List of options for $options array with their defaults. + * + * @see http://php.net/session.configuration for options + * but we omit 'session.' from the beginning of the keys for convenience. + * + * ("auto_start", is not supported as it tells PHP to start a session before + * PHP starts to execute user-land code. Setting during runtime has no effect). + * + * cache_limiter, "" (use "0" to prevent headers from being sent entirely). + * cookie_domain, "" + * cookie_httponly, "" + * cookie_lifetime, "0" + * cookie_path, "/" + * cookie_secure, "" + * entropy_file, "" + * entropy_length, "0" + * gc_divisor, "100" + * gc_maxlifetime, "1440" + * gc_probability, "1" + * hash_bits_per_character, "4" + * hash_function, "0" + * name, "PHPSESSID" + * referer_check, "" + * serialize_handler, "php" + * use_strict_mode, "0" + * use_cookies, "1" + * use_only_cookies, "1" + * use_trans_sid, "0" + * upload_progress.enabled, "1" + * upload_progress.cleanup, "1" + * upload_progress.prefix, "upload_progress_" + * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS" + * upload_progress.freq, "1%" + * upload_progress.min-freq, "1" + * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset=" + * + * @param array $options Session configuration options + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler + * @param MetadataBag $metaBag MetadataBag + */ + public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null) + { + session_cache_limiter(''); // disable by default because it's managed by HeaderBag (if used) + ini_set('session.use_cookies', 1); + + session_register_shutdown(); + + $this->setMetadataBag($metaBag); + $this->setOptions($options); + $this->setSaveHandler($handler); + } + + /** + * Gets the save handler instance. + * + * @return AbstractProxy + */ + public function getSaveHandler() + { + return $this->saveHandler; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (\PHP_SESSION_ACTIVE === session_status()) { + throw new \RuntimeException('Failed to start the session: already started by PHP.'); + } + + if (ini_get('session.use_cookies') && headers_sent($file, $line)) { + throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); + } + + // ok to try and start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session'); + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->saveHandler->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + $this->saveHandler->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->saveHandler->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->saveHandler->setName($name); + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + // Cannot regenerate the session ID for non-active sessions. + if (\PHP_SESSION_ACTIVE !== session_status()) { + return false; + } + + if (null !== $lifetime) { + ini_set('session.cookie_lifetime', $lifetime); + } + + if ($destroy) { + $this->metadataBag->stampNew(); + } + + $isRegenerated = session_regenerate_id($destroy); + + // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it. + // @see https://bugs.php.net/bug.php?id=70013 + $this->loadSession(); + + return $isRegenerated; + } + + /** + * {@inheritdoc} + */ + public function save() + { + // Register custom error handler to catch a possible failure warning during session write + set_error_handler(function ($errno, $errstr, $errfile, $errline, $errcontext) { + throw new ContextErrorException($errstr, $errno, E_WARNING, $errfile, $errline, $errcontext); + }, E_WARNING); + + try { + session_write_close(); + restore_error_handler(); + } catch (ContextErrorException $e) { + // The default PHP error message is not very helpful, as it does not give any information on the current save handler. + // Therefore, we catch this error and trigger a warning with a better error message + $handler = $this->getSaveHandler(); + if ($handler instanceof SessionHandlerProxy) { + $handler = $handler->getHandler(); + } + + restore_error_handler(); + trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING); + } + + $this->closed = true; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $_SESSION = array(); + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + if ($this->started) { + throw new \LogicException('Cannot register a bag when the session is already started.'); + } + + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if ($this->saveHandler->isActive() && !$this->started) { + $this->loadSession(); + } elseif (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * Sets the MetadataBag. + * + * @param MetadataBag $metaBag + */ + public function setMetadataBag(MetadataBag $metaBag = null) + { + if (null === $metaBag) { + $metaBag = new MetadataBag(); + } + + $this->metadataBag = $metaBag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets session.* ini variables. + * + * For convenience we omit 'session.' from the beginning of the keys. + * Explicitly ignores other ini keys. + * + * @param array $options Session ini directives array(key => value) + * + * @see http://php.net/session.configuration + */ + public function setOptions(array $options) + { + $validOptions = array_flip(array( + 'cache_limiter', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_secure', + 'entropy_file', 'entropy_length', 'gc_divisor', + 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character', + 'hash_function', 'name', 'referer_check', + 'serialize_handler', 'use_strict_mode', 'use_cookies', + 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', + 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', + 'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags', + )); + + foreach ($options as $key => $value) { + if (isset($validOptions[$key])) { + ini_set('session.'.$key, $value); + } + } + } + + /** + * Registers session save handler as a PHP session handler. + * + * To use internal PHP session save handlers, override this method using ini_set with + * session.save_handler and session.save_path e.g. + * + * ini_set('session.save_handler', 'files'); + * ini_set('session.save_path', '/tmp'); + * + * or pass in a NativeSessionHandler instance which configures session.save_handler in the + * constructor, for a template see NativeFileSessionHandler or use handlers in + * composer package drak/native-session + * + * @see http://php.net/session-set-save-handler + * @see http://php.net/sessionhandlerinterface + * @see http://php.net/sessionhandler + * @see http://github.com/drak/NativeSession + * + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $saveHandler + * + * @throws \InvalidArgumentException + */ + public function setSaveHandler($saveHandler = null) + { + if (!$saveHandler instanceof AbstractProxy && + !$saveHandler instanceof NativeSessionHandler && + !$saveHandler instanceof \SessionHandlerInterface && + null !== $saveHandler) { + throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.'); + } + + // Wrap $saveHandler in proxy and prevent double wrapping of proxy + if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { + $saveHandler = new SessionHandlerProxy($saveHandler); + } elseif (!$saveHandler instanceof AbstractProxy) { + $saveHandler = new SessionHandlerProxy(new \SessionHandler()); + } + $this->saveHandler = $saveHandler; + + if ($this->saveHandler instanceof \SessionHandlerInterface) { + session_set_save_handler($this->saveHandler, false); + } + } + + /** + * Load the session with attributes. + * + * After starting the session, PHP retrieves the session from whatever handlers + * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). + * PHP takes the return value from the read() handler, unserializes it + * and populates $_SESSION with the result automatically. + * + * @param array|null $session + */ + protected function loadSession(array &$session = null) + { + if (null === $session) { + $session = &$_SESSION; + } + + $bags = array_merge($this->bags, array($this->metadataBag)); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $session[$key] = isset($session[$key]) ? $session[$key] : array(); + $bag->initialize($session[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php new file mode 100644 index 00000000..6f02a7fd --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.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\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; + +/** + * Allows session to be started by PHP and managed by Symfony. + * + * @author Drak + */ +class PhpBridgeSessionStorage extends NativeSessionStorage +{ + /** + * Constructor. + * + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler + * @param MetadataBag $metaBag MetadataBag + */ + public function __construct($handler = null, MetadataBag $metaBag = null) + { + $this->setMetadataBag($metaBag); + $this->setSaveHandler($handler); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags and nothing else that may be set + // since the purpose of this driver is to share a handler + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // reconnect the bags to the session + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php new file mode 100644 index 00000000..a7478656 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * AbstractProxy. + * + * @author Drak + */ +abstract class AbstractProxy +{ + /** + * Flag if handler wraps an internal PHP session handler (using \SessionHandler). + * + * @var bool + */ + protected $wrapper = false; + + /** + * @var string + */ + protected $saveHandlerName; + + /** + * Gets the session.save_handler name. + * + * @return string + */ + public function getSaveHandlerName() + { + return $this->saveHandlerName; + } + + /** + * Is this proxy handler and instance of \SessionHandlerInterface. + * + * @return bool + */ + public function isSessionHandlerInterface() + { + return $this instanceof \SessionHandlerInterface; + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return bool + */ + public function isWrapper() + { + return $this->wrapper; + } + + /** + * Has a session started? + * + * @return bool + */ + public function isActive() + { + return \PHP_SESSION_ACTIVE === session_status(); + } + + /** + * Gets the session ID. + * + * @return string + */ + public function getId() + { + return session_id(); + } + + /** + * Sets the session ID. + * + * @param string $id + * + * @throws \LogicException + */ + public function setId($id) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the ID of an active session'); + } + + session_id($id); + } + + /** + * Gets the session name. + * + * @return string + */ + public function getName() + { + return session_name(); + } + + /** + * Sets the session name. + * + * @param string $name + * + * @throws \LogicException + */ + public function setName($name) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the name of an active session'); + } + + session_name($name); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php new file mode 100644 index 00000000..0db34aa2 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * NativeProxy. + * + * This proxy is built-in session handlers in PHP 5.3.x + * + * @author Drak + */ +class NativeProxy extends AbstractProxy +{ + /** + * Constructor. + */ + public function __construct() + { + // this makes an educated guess as to what the handler is since it should already be set. + $this->saveHandlerName = ini_get('session.save_handler'); + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return bool False + */ + public function isWrapper() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php new file mode 100644 index 00000000..68ed713c --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * SessionHandler proxy. + * + * @author Drak + */ +class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface +{ + /** + * @var \SessionHandlerInterface + */ + protected $handler; + + /** + * Constructor. + * + * @param \SessionHandlerInterface $handler + */ + public function __construct(\SessionHandlerInterface $handler) + { + $this->handler = $handler; + $this->wrapper = ($handler instanceof \SessionHandler); + $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user'; + } + + /** + * @return \SessionHandlerInterface + */ + public function getHandler() + { + return $this->handler; + } + + // \SessionHandlerInterface + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return (bool) $this->handler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + public function close() + { + return (bool) $this->handler->close(); + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return (string) $this->handler->read($sessionId); + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return (bool) $this->handler->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return (bool) $this->handler->destroy($sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return (bool) $this->handler->gc($maxlifetime); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 00000000..34f6c463 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * StorageInterface. + * + * @author Fabien Potencier + * @author Drak + */ +interface SessionStorageInterface +{ + /** + * Starts the session. + * + * @return bool True if started + * + * @throws \RuntimeException If something goes wrong starting the session. + */ + public function start(); + + /** + * Checks if the session is started. + * + * @return bool True if started, false otherwise + */ + public function isStarted(); + + /** + * Returns the session ID. + * + * @return string The session ID or empty + */ + public function getId(); + + /** + * Sets the session ID. + * + * @param string $id + */ + public function setId($id); + + /** + * Returns the session name. + * + * @return mixed The session name + */ + public function getName(); + + /** + * Sets the session name. + * + * @param string $name + */ + public function setName($name); + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * Note regenerate+destroy should not clear the session data in memory + * only delete the session data from persistent storage. + * + * Care: When regenerating the session ID no locking is involved in PHP's + * session design. See https://bugs.php.net/bug.php?id=61470 for a discussion. + * So you must make sure the regenerated session is saved BEFORE sending the + * headers with the new ID. Symfony's HttpKernel offers a listener for this. + * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener. + * Otherwise session data could get lost again for concurrent requests with the + * new ID. One result could be that you get logged out after just logging in. + * + * @param bool $destroy Destroy session when regenerating? + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session regenerated, false if error + * + * @throws \RuntimeException If an error occurs while regenerating this storage + */ + public function regenerate($destroy = false, $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case + * it should actually persist the session data if required. + * + * @throws \RuntimeException If the session is saved without being started, or if the session + * is already closed. + */ + public function save(); + + /** + * Clear all session data in memory. + */ + public function clear(); + + /** + * Gets a SessionBagInterface by name. + * + * @param string $name + * + * @return SessionBagInterface + * + * @throws \InvalidArgumentException If the bag does not exist + */ + public function getBag($name); + + /** + * Registers a SessionBagInterface for use. + * + * @param SessionBagInterface $bag + */ + public function registerBag(SessionBagInterface $bag); + + /** + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/StreamedResponse.php b/vendor/symfony/http-foundation/StreamedResponse.php new file mode 100644 index 00000000..92853130 --- /dev/null +++ b/vendor/symfony/http-foundation/StreamedResponse.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedResponse represents a streamed HTTP response. + * + * A StreamedResponse uses a callback for its content. + * + * The callback should use the standard PHP functions like echo + * to stream the response back to the client. The flush() method + * can also be used if needed. + * + * @see flush() + * + * @author Fabien Potencier + */ +class StreamedResponse extends Response +{ + protected $callback; + protected $streamed; + private $headersSent; + + /** + * Constructor. + * + * @param callable|null $callback A valid PHP callback or null to set it later + * @param int $status The response status code + * @param array $headers An array of response headers + */ + public function __construct(callable $callback = null, $status = 200, $headers = array()) + { + parent::__construct(null, $status, $headers); + + if (null !== $callback) { + $this->setCallback($callback); + } + $this->streamed = false; + $this->headersSent = false; + } + + /** + * Factory method for chainability. + * + * @param callable|null $callback A valid PHP callback or null to set it later + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($callback = null, $status = 200, $headers = array()) + { + return new static($callback, $status, $headers); + } + + /** + * Sets the PHP callback associated with this Response. + * + * @param callable $callback A valid PHP callback + */ + public function setCallback(callable $callback) + { + $this->callback = $callback; + } + + /** + * {@inheritdoc} + * + * This method only sends the headers once. + */ + public function sendHeaders() + { + if ($this->headersSent) { + return; + } + + $this->headersSent = true; + + parent::sendHeaders(); + } + + /** + * {@inheritdoc} + * + * This method only sends the content once. + */ + public function sendContent() + { + if ($this->streamed) { + return; + } + + $this->streamed = true; + + if (null === $this->callback) { + throw new \LogicException('The Response callback must not be null.'); + } + + call_user_func($this->callback); + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent($content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); + } + } + + /** + * {@inheritdoc} + * + * @return false + */ + public function getContent() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php b/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php new file mode 100644 index 00000000..cb43bb35 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\AcceptHeaderItem; + +class AcceptHeaderItemTest extends TestCase +{ + /** + * @dataProvider provideFromStringData + */ + public function testFromString($string, $value, array $attributes) + { + $item = AcceptHeaderItem::fromString($string); + $this->assertEquals($value, $item->getValue()); + $this->assertEquals($attributes, $item->getAttributes()); + } + + public function provideFromStringData() + { + return array( + array( + 'text/html', + 'text/html', array(), + ), + array( + '"this;should,not=matter"', + 'this;should,not=matter', array(), + ), + array( + "text/plain; charset=utf-8;param=\"this;should,not=matter\";\tfootnotes=true", + 'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'), + ), + array( + '"this;should,not=matter";charset=utf-8', + 'this;should,not=matter', array('charset' => 'utf-8'), + ), + ); + } + + /** + * @dataProvider provideToStringData + */ + public function testToString($value, array $attributes, $string) + { + $item = new AcceptHeaderItem($value, $attributes); + $this->assertEquals($string, (string) $item); + } + + public function provideToStringData() + { + return array( + array( + 'text/html', array(), + 'text/html', + ), + array( + 'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'), + 'text/plain;charset=utf-8;param="this;should,not=matter";footnotes=true', + ), + ); + } + + public function testValue() + { + $item = new AcceptHeaderItem('value', array()); + $this->assertEquals('value', $item->getValue()); + + $item->setValue('new value'); + $this->assertEquals('new value', $item->getValue()); + + $item->setValue(1); + $this->assertEquals('1', $item->getValue()); + } + + public function testQuality() + { + $item = new AcceptHeaderItem('value', array()); + $this->assertEquals(1.0, $item->getQuality()); + + $item->setQuality(0.5); + $this->assertEquals(0.5, $item->getQuality()); + + $item->setAttribute('q', 0.75); + $this->assertEquals(0.75, $item->getQuality()); + $this->assertFalse($item->hasAttribute('q')); + } + + public function testAttribute() + { + $item = new AcceptHeaderItem('value', array()); + $this->assertEquals(array(), $item->getAttributes()); + $this->assertFalse($item->hasAttribute('test')); + $this->assertNull($item->getAttribute('test')); + $this->assertEquals('default', $item->getAttribute('test', 'default')); + + $item->setAttribute('test', 'value'); + $this->assertEquals(array('test' => 'value'), $item->getAttributes()); + $this->assertTrue($item->hasAttribute('test')); + $this->assertEquals('value', $item->getAttribute('test')); + $this->assertEquals('value', $item->getAttribute('test', 'default')); + } +} diff --git a/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php b/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.php new file mode 100644 index 00000000..9929eac2 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.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\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\AcceptHeader; +use Symfony\Component\HttpFoundation\AcceptHeaderItem; + +class AcceptHeaderTest extends TestCase +{ + public function testFirst() + { + $header = AcceptHeader::fromString('text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c'); + $this->assertSame('text/html', $header->first()->getValue()); + } + + /** + * @dataProvider provideFromStringData + */ + public function testFromString($string, array $items) + { + $header = AcceptHeader::fromString($string); + $parsed = array_values($header->all()); + // reset index since the fixtures don't have them set + foreach ($parsed as $item) { + $item->setIndex(0); + } + $this->assertEquals($items, $parsed); + } + + public function provideFromStringData() + { + return array( + array('', array()), + array('gzip', array(new AcceptHeaderItem('gzip'))), + array('gzip,deflate,sdch', array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))), + array("gzip, deflate\t,sdch", array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))), + array('"this;should,not=matter"', array(new AcceptHeaderItem('this;should,not=matter'))), + ); + } + + /** + * @dataProvider provideToStringData + */ + public function testToString(array $items, $string) + { + $header = new AcceptHeader($items); + $this->assertEquals($string, (string) $header); + } + + public function provideToStringData() + { + return array( + array(array(), ''), + array(array(new AcceptHeaderItem('gzip')), 'gzip'), + array(array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch')), 'gzip,deflate,sdch'), + array(array(new AcceptHeaderItem('this;should,not=matter')), 'this;should,not=matter'), + ); + } + + /** + * @dataProvider provideFilterData + */ + public function testFilter($string, $filter, array $values) + { + $header = AcceptHeader::fromString($string)->filter($filter); + $this->assertEquals($values, array_keys($header->all())); + } + + public function provideFilterData() + { + return array( + array('fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4', '/fr.*/', array('fr-FR', 'fr')), + ); + } + + /** + * @dataProvider provideSortingData + */ + public function testSorting($string, array $values) + { + $header = AcceptHeader::fromString($string); + $this->assertEquals($values, array_keys($header->all())); + } + + public function provideSortingData() + { + return array( + 'quality has priority' => array('*;q=0.3,ISO-8859-1,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')), + 'order matters when q is equal' => array('*;q=0.3,ISO-8859-1;q=0.7,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')), + 'order matters when q is equal2' => array('*;q=0.3,utf-8;q=0.7,ISO-8859-1;q=0.7', array('utf-8', 'ISO-8859-1', '*')), + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php b/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php new file mode 100644 index 00000000..157ab90e --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ApacheRequestTest.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\ApacheRequest; + +class ApacheRequestTest extends TestCase +{ + /** + * @dataProvider provideServerVars + */ + public function testUriMethods($server, $expectedRequestUri, $expectedBaseUrl, $expectedPathInfo) + { + $request = new ApacheRequest(); + $request->server->replace($server); + + $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct'); + $this->assertEquals($expectedBaseUrl, $request->getBaseUrl(), '->getBaseUrl() is correct'); + $this->assertEquals($expectedPathInfo, $request->getPathInfo(), '->getPathInfo() is correct'); + } + + public function provideServerVars() + { + return array( + array( + array( + 'REQUEST_URI' => '/foo/app_dev.php/bar', + 'SCRIPT_NAME' => '/foo/app_dev.php', + 'PATH_INFO' => '/bar', + ), + '/foo/app_dev.php/bar', + '/foo/app_dev.php', + '/bar', + ), + array( + array( + 'REQUEST_URI' => '/foo/bar', + 'SCRIPT_NAME' => '/foo/app_dev.php', + ), + '/foo/bar', + '/foo', + '/bar', + ), + array( + array( + 'REQUEST_URI' => '/app_dev.php/foo/bar', + 'SCRIPT_NAME' => '/app_dev.php', + 'PATH_INFO' => '/foo/bar', + ), + '/app_dev.php/foo/bar', + '/app_dev.php', + '/foo/bar', + ), + array( + array( + 'REQUEST_URI' => '/foo/bar', + 'SCRIPT_NAME' => '/app_dev.php', + ), + '/foo/bar', + '', + '/foo/bar', + ), + array( + array( + 'REQUEST_URI' => '/app_dev.php', + 'SCRIPT_NAME' => '/app_dev.php', + ), + '/app_dev.php', + '/app_dev.php', + '/', + ), + array( + array( + 'REQUEST_URI' => '/', + 'SCRIPT_NAME' => '/app_dev.php', + ), + '/', + '', + '/', + ), + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php b/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php new file mode 100644 index 00000000..89e078ca --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.php @@ -0,0 +1,341 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\File\Stream; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\Tests\File\FakeFile; + +class BinaryFileResponseTest extends ResponseTestCase +{ + public function testConstruction() + { + $file = __DIR__.'/../README.md'; + $response = new BinaryFileResponse($file, 404, array('X-Header' => 'Foo'), true, null, true, true); + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals('Foo', $response->headers->get('X-Header')); + $this->assertTrue($response->headers->has('ETag')); + $this->assertTrue($response->headers->has('Last-Modified')); + $this->assertFalse($response->headers->has('Content-Disposition')); + + $response = BinaryFileResponse::create($file, 404, array(), true, ResponseHeaderBag::DISPOSITION_INLINE); + $this->assertEquals(404, $response->getStatusCode()); + $this->assertFalse($response->headers->has('ETag')); + $this->assertEquals('inline; filename="README.md"', $response->headers->get('Content-Disposition')); + } + + public function testConstructWithNonAsciiFilename() + { + touch(sys_get_temp_dir().'/fööö.html'); + + $response = new BinaryFileResponse(sys_get_temp_dir().'/fööö.html', 200, array(), true, 'attachment'); + + @unlink(sys_get_temp_dir().'/fööö.html'); + + $this->assertSame('fööö.html', $response->getFile()->getFilename()); + } + + /** + * @expectedException \LogicException + */ + public function testSetContent() + { + $response = new BinaryFileResponse(__FILE__); + $response->setContent('foo'); + } + + public function testGetContent() + { + $response = new BinaryFileResponse(__FILE__); + $this->assertFalse($response->getContent()); + } + + public function testSetContentDispositionGeneratesSafeFallbackFilename() + { + $response = new BinaryFileResponse(__FILE__); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'föö.html'); + + $this->assertSame('attachment; filename="f__.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html', $response->headers->get('Content-Disposition')); + } + + /** + * @dataProvider provideRanges + */ + public function testRequests($requestRange, $offset, $length, $responseRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag(); + + // do a request to get the ETag + $request = Request::create('/'); + $response->prepare($request); + $etag = $response->headers->get('ETag'); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('If-Range', $etag); + $request->headers->set('Range', $requestRange); + + $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r'); + fseek($file, $offset); + $data = fread($file, $length); + fclose($file); + + $this->expectOutputString($data); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(206, $response->getStatusCode()); + $this->assertEquals($responseRange, $response->headers->get('Content-Range')); + $this->assertSame($length, $response->headers->get('Content-Length')); + } + + /** + * @dataProvider provideRanges + */ + public function testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream')); + + // do a request to get the LastModified + $request = Request::create('/'); + $response->prepare($request); + $lastModified = $response->headers->get('Last-Modified'); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('If-Range', $lastModified); + $request->headers->set('Range', $requestRange); + + $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r'); + fseek($file, $offset); + $data = fread($file, $length); + fclose($file); + + $this->expectOutputString($data); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(206, $response->getStatusCode()); + $this->assertEquals($responseRange, $response->headers->get('Content-Range')); + } + + public function provideRanges() + { + return array( + array('bytes=1-4', 1, 4, 'bytes 1-4/35'), + array('bytes=-5', 30, 5, 'bytes 30-34/35'), + array('bytes=30-', 30, 5, 'bytes 30-34/35'), + array('bytes=30-30', 30, 1, 'bytes 30-30/35'), + array('bytes=30-34', 30, 5, 'bytes 30-34/35'), + ); + } + + public function testRangeRequestsWithoutLastModifiedDate() + { + // prevent auto last modified + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'), true, null, false, false); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('If-Range', date('D, d M Y H:i:s').' GMT'); + $request->headers->set('Range', 'bytes=1-4'); + + $this->expectOutputString(file_get_contents(__DIR__.'/File/Fixtures/test.gif')); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertNull($response->headers->get('Content-Range')); + } + + /** + * @dataProvider provideFullFileRanges + */ + public function testFullFileRequests($requestRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag(); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('Range', $requestRange); + + $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r'); + $data = fread($file, 35); + fclose($file); + + $this->expectOutputString($data); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(200, $response->getStatusCode()); + } + + public function provideFullFileRanges() + { + return array( + array('bytes=0-'), + array('bytes=0-34'), + array('bytes=-35'), + // Syntactical invalid range-request should also return the full resource + array('bytes=20-10'), + array('bytes=50-40'), + ); + } + + /** + * @dataProvider provideInvalidRanges + */ + public function testInvalidRequests($requestRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'))->setAutoEtag(); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('Range', $requestRange); + + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(416, $response->getStatusCode()); + $this->assertEquals('bytes */35', $response->headers->get('Content-Range')); + } + + public function provideInvalidRanges() + { + return array( + array('bytes=-40'), + array('bytes=30-40'), + ); + } + + /** + * @dataProvider provideXSendfileFiles + */ + public function testXSendfile($file) + { + $request = Request::create('/'); + $request->headers->set('X-Sendfile-Type', 'X-Sendfile'); + + BinaryFileResponse::trustXSendfileTypeHeader(); + $response = BinaryFileResponse::create($file, 200, array('Content-Type' => 'application/octet-stream')); + $response->prepare($request); + + $this->expectOutputString(''); + $response->sendContent(); + + $this->assertContains('README.md', $response->headers->get('X-Sendfile')); + } + + public function provideXSendfileFiles() + { + return array( + array(__DIR__.'/../README.md'), + array('file://'.__DIR__.'/../README.md'), + ); + } + + /** + * @dataProvider getSampleXAccelMappings + */ + public function testXAccelMapping($realpath, $mapping, $virtual) + { + $request = Request::create('/'); + $request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect'); + $request->headers->set('X-Accel-Mapping', $mapping); + + $file = new FakeFile($realpath, __DIR__.'/File/Fixtures/test'); + + BinaryFileResponse::trustXSendfileTypeHeader(); + $response = new BinaryFileResponse($file, 200, array('Content-Type' => 'application/octet-stream')); + $reflection = new \ReflectionObject($response); + $property = $reflection->getProperty('file'); + $property->setAccessible(true); + $property->setValue($response, $file); + + $response->prepare($request); + $this->assertEquals($virtual, $response->headers->get('X-Accel-Redirect')); + } + + public function testDeleteFileAfterSend() + { + $request = Request::create('/'); + + $path = __DIR__.'/File/Fixtures/to_delete'; + touch($path); + $realPath = realpath($path); + $this->assertFileExists($realPath); + + $response = new BinaryFileResponse($realPath, 200, array('Content-Type' => 'application/octet-stream')); + $response->deleteFileAfterSend(true); + + $response->prepare($request); + $response->sendContent(); + + $this->assertFileNotExists($path); + } + + public function testAcceptRangeOnUnsafeMethods() + { + $request = Request::create('/', 'POST'); + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream')); + $response->prepare($request); + + $this->assertEquals('none', $response->headers->get('Accept-Ranges')); + } + + public function testAcceptRangeNotOverriden() + { + $request = Request::create('/', 'POST'); + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream')); + $response->headers->set('Accept-Ranges', 'foo'); + $response->prepare($request); + + $this->assertEquals('foo', $response->headers->get('Accept-Ranges')); + } + + public function getSampleXAccelMappings() + { + return array( + array('/var/www/var/www/files/foo.txt', '/var/www/=/files/', '/files/var/www/files/foo.txt'), + array('/home/foo/bar.txt', '/var/www/=/files/,/home/foo/=/baz/', '/baz/bar.txt'), + ); + } + + public function testStream() + { + $request = Request::create('/'); + $response = new BinaryFileResponse(new Stream(__DIR__.'/../README.md'), 200, array('Content-Type' => 'text/plain')); + $response->prepare($request); + + $this->assertNull($response->headers->get('Content-Length')); + } + + protected function provideResponse() + { + return new BinaryFileResponse(__DIR__.'/../README.md', 200, array('Content-Type' => 'application/octet-stream')); + } + + public static function tearDownAfterClass() + { + $path = __DIR__.'/../Fixtures/to_delete'; + if (file_exists($path)) { + @unlink($path); + } + } +} diff --git a/vendor/symfony/http-foundation/Tests/CookieTest.php b/vendor/symfony/http-foundation/Tests/CookieTest.php new file mode 100644 index 00000000..edaed253 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/CookieTest.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Cookie; + +/** + * CookieTest. + * + * @author John Kary + * @author Hugo Hamon + * + * @group time-sensitive + */ +class CookieTest extends TestCase +{ + public function invalidNames() + { + return array( + array(''), + array(',MyName'), + array(';MyName'), + array(' MyName'), + array("\tMyName"), + array("\rMyName"), + array("\nMyName"), + array("\013MyName"), + array("\014MyName"), + ); + } + + /** + * @dataProvider invalidNames + * @expectedException \InvalidArgumentException + */ + public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name) + { + new Cookie($name); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidExpiration() + { + new Cookie('MyCookie', 'foo', 'bar'); + } + + public function testNegativeExpirationIsNotPossible() + { + $cookie = new Cookie('foo', 'bar', -100); + + $this->assertSame(0, $cookie->getExpiresTime()); + } + + public function testGetValue() + { + $value = 'MyValue'; + $cookie = new Cookie('MyCookie', $value); + + $this->assertSame($value, $cookie->getValue(), '->getValue() returns the proper value'); + } + + public function testGetPath() + { + $cookie = new Cookie('foo', 'bar'); + + $this->assertSame('/', $cookie->getPath(), '->getPath() returns / as the default path'); + } + + public function testGetExpiresTime() + { + $cookie = new Cookie('foo', 'bar'); + + $this->assertEquals(0, $cookie->getExpiresTime(), '->getExpiresTime() returns the default expire date'); + + $cookie = new Cookie('foo', 'bar', $expire = time() + 3600); + + $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + public function testGetExpiresTimeIsCastToInt() + { + $cookie = new Cookie('foo', 'bar', 3600.9); + + $this->assertSame(3600, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date as an integer'); + } + + public function testConstructorWithDateTime() + { + $expire = new \DateTime(); + $cookie = new Cookie('foo', 'bar', $expire); + + $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + /** + * @requires PHP 5.5 + */ + public function testConstructorWithDateTimeImmutable() + { + $expire = new \DateTimeImmutable(); + $cookie = new Cookie('foo', 'bar', $expire); + + $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + public function testGetExpiresTimeWithStringValue() + { + $value = '+1 day'; + $cookie = new Cookie('foo', 'bar', $value); + $expire = strtotime($value); + + $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date', 1); + } + + public function testGetDomain() + { + $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com'); + + $this->assertEquals('.myfoodomain.com', $cookie->getDomain(), '->getDomain() returns the domain name on which the cookie is valid'); + } + + public function testIsSecure() + { + $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', true); + + $this->assertTrue($cookie->isSecure(), '->isSecure() returns whether the cookie is transmitted over HTTPS'); + } + + public function testIsHttpOnly() + { + $cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', false, true); + + $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns whether the cookie is only transmitted over HTTP'); + } + + public function testCookieIsNotCleared() + { + $cookie = new Cookie('foo', 'bar', time() + 3600 * 24); + + $this->assertFalse($cookie->isCleared(), '->isCleared() returns false if the cookie did not expire yet'); + } + + public function testCookieIsCleared() + { + $cookie = new Cookie('foo', 'bar', time() - 20); + + $this->assertTrue($cookie->isCleared(), '->isCleared() returns true if the cookie has expired'); + } + + public function testToString() + { + $cookie = new Cookie('foo', 'bar', $expire = strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true); + $this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie'); + + $cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com'); + $this->assertEquals('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', $expire = time() - 31536001).'; max-age='.($expire - time()).'; path=/admin/; domain=.myfoodomain.com; httponly', (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL'); + + $cookie = new Cookie('foo', 'bar', 0, '/', ''); + $this->assertEquals('foo=bar; path=/; httponly', (string) $cookie); + } + + public function testRawCookie() + { + $cookie = new Cookie('foo', 'b a r', 0, '/', null, false, false); + $this->assertFalse($cookie->isRaw()); + $this->assertEquals('foo=b+a+r; path=/', (string) $cookie); + + $cookie = new Cookie('foo', 'b+a+r', 0, '/', null, false, false, true); + $this->assertTrue($cookie->isRaw()); + $this->assertEquals('foo=b+a+r; path=/', (string) $cookie); + } + + public function testGetMaxAge() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals(0, $cookie->getMaxAge()); + + $cookie = new Cookie('foo', 'bar', $expire = time() + 100); + $this->assertEquals($expire - time(), $cookie->getMaxAge()); + + $cookie = new Cookie('foo', 'bar', $expire = time() - 100); + $this->assertEquals($expire - time(), $cookie->getMaxAge()); + } + + public function testFromString() + { + $cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly'); + $this->assertEquals(new Cookie('foo', 'bar', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true, true, true), $cookie); + + $cookie = Cookie::fromString('foo=bar', true); + $this->assertEquals(new Cookie('foo', 'bar'), $cookie); + } +} diff --git a/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php new file mode 100644 index 00000000..1152e46c --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\ExpressionRequestMatcher; +use Symfony\Component\HttpFoundation\Request; + +class ExpressionRequestMatcherTest extends TestCase +{ + /** + * @expectedException \LogicException + */ + public function testWhenNoExpressionIsSet() + { + $expressionRequestMatcher = new ExpressionRequestMatcher(); + $expressionRequestMatcher->matches(new Request()); + } + + /** + * @dataProvider provideExpressions + */ + public function testMatchesWhenParentMatchesIsTrue($expression, $expected) + { + $request = Request::create('/foo'); + $expressionRequestMatcher = new ExpressionRequestMatcher(); + + $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression); + $this->assertSame($expected, $expressionRequestMatcher->matches($request)); + } + + /** + * @dataProvider provideExpressions + */ + public function testMatchesWhenParentMatchesIsFalse($expression) + { + $request = Request::create('/foo'); + $request->attributes->set('foo', 'foo'); + $expressionRequestMatcher = new ExpressionRequestMatcher(); + $expressionRequestMatcher->matchAttribute('foo', 'bar'); + + $expressionRequestMatcher->setExpression(new ExpressionLanguage(), $expression); + $this->assertFalse($expressionRequestMatcher->matches($request)); + } + + public function provideExpressions() + { + return array( + array('request.getMethod() == method', true), + array('request.getPathInfo() == path', true), + array('request.getHost() == host', true), + array('request.getClientIp() == ip', true), + array('request.attributes.all() == attributes', true), + array('request.getMethod() == method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip && request.attributes.all() == attributes', true), + array('request.getMethod() != method', false), + array('request.getMethod() != method && request.getPathInfo() == path && request.getHost() == host && request.getClientIp() == ip && request.attributes.all() == attributes', false), + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/File/FakeFile.php b/vendor/symfony/http-foundation/Tests/File/FakeFile.php new file mode 100644 index 00000000..c415989f --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/FakeFile.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use Symfony\Component\HttpFoundation\File\File as OrigFile; + +class FakeFile extends OrigFile +{ + private $realpath; + + public function __construct($realpath, $path) + { + $this->realpath = $realpath; + parent::__construct($path, false); + } + + public function isReadable() + { + return true; + } + + public function getRealpath() + { + return $this->realpath; + } + + public function getSize() + { + return 42; + } + + public function getMTime() + { + return time(); + } +} diff --git a/vendor/symfony/http-foundation/Tests/File/FileTest.php b/vendor/symfony/http-foundation/Tests/File/FileTest.php new file mode 100644 index 00000000..dbd9c44b --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/FileTest.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; + +class FileTest extends TestCase +{ + protected $file; + + public function testGetMimeTypeUsesMimeTypeGuessers() + { + $file = new File(__DIR__.'/Fixtures/test.gif'); + $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif'); + + MimeTypeGuesser::getInstance()->register($guesser); + + $this->assertEquals('image/gif', $file->getMimeType()); + } + + public function testGuessExtensionWithoutGuesser() + { + $file = new File(__DIR__.'/Fixtures/directory/.empty'); + + $this->assertNull($file->guessExtension()); + } + + public function testGuessExtensionIsBasedOnMimeType() + { + $file = new File(__DIR__.'/Fixtures/test'); + $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif'); + + MimeTypeGuesser::getInstance()->register($guesser); + + $this->assertEquals('gif', $file->guessExtension()); + } + + /** + * @requires extension fileinfo + */ + public function testGuessExtensionWithReset() + { + $file = new File(__DIR__.'/Fixtures/other-file.example'); + $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif'); + MimeTypeGuesser::getInstance()->register($guesser); + + $this->assertEquals('gif', $file->guessExtension()); + + MimeTypeGuesser::reset(); + + $this->assertNull($file->guessExtension()); + } + + public function testConstructWhenFileNotExists() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + new File(__DIR__.'/Fixtures/not_here'); + } + + public function testMove() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory'; + $targetPath = $targetDir.'/test.copy.gif'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + $movedFile = $file->move($targetDir); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($path); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function testMoveWithNewName() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory'; + $targetPath = $targetDir.'/test.newname.gif'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + $movedFile = $file->move($targetDir, 'test.newname.gif'); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($path); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function getFilenameFixtures() + { + return array( + array('original.gif', 'original.gif'), + array('..\\..\\original.gif', 'original.gif'), + array('../../original.gif', 'original.gif'), + array('файлfile.gif', 'файлfile.gif'), + array('..\\..\\файлfile.gif', 'файлfile.gif'), + array('../../файлfile.gif', 'файлfile.gif'), + ); + } + + /** + * @dataProvider getFilenameFixtures + */ + public function testMoveWithNonLatinName($filename, $sanitizedFilename) + { + $path = __DIR__.'/Fixtures/'.$sanitizedFilename; + $targetDir = __DIR__.'/Fixtures/directory/'; + $targetPath = $targetDir.$sanitizedFilename; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + $movedFile = $file->move($targetDir, $filename); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($path); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function testMoveToAnUnexistentDirectory() + { + $sourcePath = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory/sub'; + $targetPath = $targetDir.'/test.copy.gif'; + @unlink($sourcePath); + @unlink($targetPath); + @rmdir($targetDir); + copy(__DIR__.'/Fixtures/test.gif', $sourcePath); + + $file = new File($sourcePath); + $movedFile = $file->move($targetDir); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($sourcePath); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($sourcePath); + @unlink($targetPath); + @rmdir($targetDir); + } + + protected function createMockGuesser($path, $mimeType) + { + $guesser = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface')->getMock(); + $guesser + ->expects($this->once()) + ->method('guess') + ->with($this->equalTo($path)) + ->will($this->returnValue($mimeType)) + ; + + return $guesser; + } +} diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension b/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension new file mode 100644 index 00000000..4d1ae35b --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/Fixtures/.unknownextension @@ -0,0 +1 @@ +f \ No newline at end of file diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty b/vendor/symfony/http-foundation/Tests/File/Fixtures/directory/.empty new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example b/vendor/symfony/http-foundation/Tests/File/Fixtures/other-file.example new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/test b/vendor/symfony/http-foundation/Tests/File/Fixtures/test new file mode 100644 index 0000000000000000000000000000000000000000..b636f4b8df536b0a85e7cea1a6cf3f0bd3179b96 GIT binary patch literal 35 jcmZ?wbh9u|WMp7uXkcLY4+c66KmZb9U}AD%WUvMRyAlZ1 literal 0 HcmV?d00001 diff --git a/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif b/vendor/symfony/http-foundation/Tests/File/Fixtures/test.gif new file mode 100644 index 0000000000000000000000000000000000000000..b636f4b8df536b0a85e7cea1a6cf3f0bd3179b96 GIT binary patch literal 35 jcmZ?wbh9u|WMp7uXkcLY4+c66KmZb9U}AD%WUvMRyAlZ1 literal 0 HcmV?d00001 diff --git a/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php b/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php new file mode 100644 index 00000000..5a2b7a21 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File\MimeType; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser; + +/** + * @requires extension fileinfo + */ +class MimeTypeTest extends TestCase +{ + protected $path; + + public function testGuessImageWithoutExtension() + { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithDirectory() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/directory'); + } + + public function testGuessImageWithFileBinaryMimeTypeGuesser() + { + $guesser = MimeTypeGuesser::getInstance(); + $guesser->register(new FileBinaryMimeTypeGuesser()); + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithKnownExtension() + { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif')); + } + + public function testGuessFileWithUnknownExtension() + { + $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension')); + } + + public function testGuessWithIncorrectPath() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/not_here'); + } + + public function testGuessWithNonReadablePath() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Can not verify chmod operations on Windows'); + } + + if (!getenv('USER') || 'root' === getenv('USER')) { + $this->markTestSkipped('This test will fail if run under superuser'); + } + + $path = __DIR__.'/../Fixtures/to_delete'; + touch($path); + @chmod($path, 0333); + + if (substr(sprintf('%o', fileperms($path)), -4) == '0333') { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException'); + MimeTypeGuesser::getInstance()->guess($path); + } else { + $this->markTestSkipped('Can not verify chmod operations, change of file permissions failed'); + } + } + + public static function tearDownAfterClass() + { + $path = __DIR__.'/../Fixtures/to_delete'; + if (file_exists($path)) { + @chmod($path, 0666); + @unlink($path); + } + } +} diff --git a/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php b/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php new file mode 100644 index 00000000..36f122fe --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\File\UploadedFile; + +class UploadedFileTest extends TestCase +{ + protected function setUp() + { + if (!ini_get('file_uploads')) { + $this->markTestSkipped('file_uploads is disabled in php.ini'); + } + } + + public function testConstructWhenFileNotExists() + { + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + new UploadedFile( + __DIR__.'/Fixtures/not_here', + 'original.gif', + null + ); + } + + public function testFileUploadsWithNoMimeType() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $this->assertEquals('application/octet-stream', $file->getClientMimeType()); + + if (extension_loaded('fileinfo')) { + $this->assertEquals('image/gif', $file->getMimeType()); + } + } + + public function testFileUploadsWithUnknownMimeType() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/.unknownextension', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/.unknownextension'), + UPLOAD_ERR_OK + ); + + $this->assertEquals('application/octet-stream', $file->getClientMimeType()); + } + + public function testGuessClientExtension() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('gif', $file->guessClientExtension()); + } + + public function testGuessClientExtensionWithIncorrectMimeType() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/jpeg', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('jpeg', $file->guessClientExtension()); + } + + public function testErrorIsOkByDefault() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals(UPLOAD_ERR_OK, $file->getError()); + } + + public function testGetClientOriginalName() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('original.gif', $file->getClientOriginalName()); + } + + public function testGetClientOriginalExtension() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('gif', $file->getClientOriginalExtension()); + } + + /** + * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException + */ + public function testMoveLocalFileIsNotAllowed() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $movedFile = $file->move(__DIR__.'/Fixtures/directory'); + } + + public function testMoveLocalFileIsAllowedInTestMode() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory'; + $targetPath = $targetDir.'/test.copy.gif'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new UploadedFile( + $path, + 'original.gif', + 'image/gif', + filesize($path), + UPLOAD_ERR_OK, + true + ); + + $movedFile = $file->move(__DIR__.'/Fixtures/directory'); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($path); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function testGetClientOriginalNameSanitizeFilename() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + '../../original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('original.gif', $file->getClientOriginalName()); + } + + public function testGetSize() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals(filesize(__DIR__.'/Fixtures/test.gif'), $file->getSize()); + + $file = new UploadedFile( + __DIR__.'/Fixtures/test', + 'original.gif', + 'image/gif' + ); + + $this->assertEquals(filesize(__DIR__.'/Fixtures/test'), $file->getSize()); + } + + public function testGetExtension() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null + ); + + $this->assertEquals('gif', $file->getExtension()); + } + + public function testIsValid() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK, + true + ); + + $this->assertTrue($file->isValid()); + } + + /** + * @dataProvider uploadedFileErrorProvider + */ + public function testIsInvalidOnUploadError($error) + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + $error + ); + + $this->assertFalse($file->isValid()); + } + + public function uploadedFileErrorProvider() + { + return array( + array(UPLOAD_ERR_INI_SIZE), + array(UPLOAD_ERR_FORM_SIZE), + array(UPLOAD_ERR_PARTIAL), + array(UPLOAD_ERR_NO_TMP_DIR), + array(UPLOAD_ERR_EXTENSION), + ); + } + + public function testIsInvalidIfNotHttpUpload() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $this->assertFalse($file->isValid()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/FileBagTest.php b/vendor/symfony/http-foundation/Tests/FileBagTest.php new file mode 100644 index 00000000..e7defa67 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/FileBagTest.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\FileBag; + +/** + * FileBagTest. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBagTest extends TestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testFileMustBeAnArrayOrUploadedFile() + { + new FileBag(array('file' => 'foo')); + } + + public function testShouldConvertsUploadedFiles() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + + $bag = new FileBag(array('file' => array( + 'name' => basename($tmpFile), + 'type' => 'text/plain', + 'tmp_name' => $tmpFile, + 'error' => 0, + 'size' => 100, + ))); + + $this->assertEquals($file, $bag->get('file')); + } + + public function testShouldSetEmptyUploadedFilesToNull() + { + $bag = new FileBag(array('file' => array( + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => UPLOAD_ERR_NO_FILE, + 'size' => 0, + ))); + + $this->assertNull($bag->get('file')); + } + + public function testShouldConvertUploadedFilesWithPhpBug() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + + $bag = new FileBag(array( + 'child' => array( + 'name' => array( + 'file' => basename($tmpFile), + ), + 'type' => array( + 'file' => 'text/plain', + ), + 'tmp_name' => array( + 'file' => $tmpFile, + ), + 'error' => array( + 'file' => 0, + ), + 'size' => array( + 'file' => 100, + ), + ), + )); + + $files = $bag->all(); + $this->assertEquals($file, $files['child']['file']); + } + + public function testShouldConvertNestedUploadedFilesWithPhpBug() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + + $bag = new FileBag(array( + 'child' => array( + 'name' => array( + 'sub' => array('file' => basename($tmpFile)), + ), + 'type' => array( + 'sub' => array('file' => 'text/plain'), + ), + 'tmp_name' => array( + 'sub' => array('file' => $tmpFile), + ), + 'error' => array( + 'sub' => array('file' => 0), + ), + 'size' => array( + 'sub' => array('file' => 100), + ), + ), + )); + + $files = $bag->all(); + $this->assertEquals($file, $files['child']['sub']['file']); + } + + public function testShouldNotConvertNestedUploadedFiles() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + $bag = new FileBag(array('image' => array('file' => $file))); + + $files = $bag->all(); + $this->assertEquals($file, $files['image']['file']); + } + + protected function createTempFile() + { + return tempnam(sys_get_temp_dir().'/form_test', 'FormTest'); + } + + protected function setUp() + { + mkdir(sys_get_temp_dir().'/form_test', 0777, true); + } + + protected function tearDown() + { + foreach (glob(sys_get_temp_dir().'/form_test/*') as $file) { + unlink($file); + } + + rmdir(sys_get_temp_dir().'/form_test'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/HeaderBagTest.php b/vendor/symfony/http-foundation/Tests/HeaderBagTest.php new file mode 100644 index 00000000..1acf5930 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/HeaderBagTest.php @@ -0,0 +1,205 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\HeaderBag; + +class HeaderBagTest extends TestCase +{ + public function testConstructor() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $this->assertTrue($bag->has('foo')); + } + + public function testToStringNull() + { + $bag = new HeaderBag(); + $this->assertEquals('', $bag->__toString()); + } + + public function testToStringNotNull() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $this->assertEquals("Foo: bar\r\n", $bag->__toString()); + } + + public function testKeys() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $keys = $bag->keys(); + $this->assertEquals('foo', $keys[0]); + } + + public function testGetDate() + { + $bag = new HeaderBag(array('foo' => 'Tue, 4 Sep 2012 20:00:00 +0200')); + $headerDate = $bag->getDate('foo'); + $this->assertInstanceOf('DateTime', $headerDate); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetDateException() + { + $bag = new HeaderBag(array('foo' => 'Tue')); + $headerDate = $bag->getDate('foo'); + } + + public function testGetCacheControlHeader() + { + $bag = new HeaderBag(); + $bag->addCacheControlDirective('public', '#a'); + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertEquals('#a', $bag->getCacheControlDirective('public')); + } + + public function testAll() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $this->assertEquals(array('foo' => array('bar')), $bag->all(), '->all() gets all the input'); + + $bag = new HeaderBag(array('FOO' => 'BAR')); + $this->assertEquals(array('foo' => array('BAR')), $bag->all(), '->all() gets all the input key are lower case'); + } + + public function testReplace() + { + $bag = new HeaderBag(array('foo' => 'bar')); + + $bag->replace(array('NOPE' => 'BAR')); + $this->assertEquals(array('nope' => array('BAR')), $bag->all(), '->replace() replaces the input with the argument'); + $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input'); + } + + public function testGet() + { + $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz')); + $this->assertEquals('bar', $bag->get('foo'), '->get return current value'); + $this->assertEquals('bar', $bag->get('FoO'), '->get key in case insensitive'); + $this->assertEquals(array('bar'), $bag->get('foo', 'nope', false), '->get return the value as array'); + + // defaults + $this->assertNull($bag->get('none'), '->get unknown values returns null'); + $this->assertEquals('default', $bag->get('none', 'default'), '->get unknown values returns default'); + $this->assertEquals(array('default'), $bag->get('none', 'default', false), '->get unknown values returns default as array'); + + $bag->set('foo', 'bor', false); + $this->assertEquals('bar', $bag->get('foo'), '->get return first value'); + $this->assertEquals(array('bar', 'bor'), $bag->get('foo', 'nope', false), '->get return all values as array'); + } + + public function testSetAssociativeArray() + { + $bag = new HeaderBag(); + $bag->set('foo', array('bad-assoc-index' => 'value')); + $this->assertSame('value', $bag->get('foo')); + $this->assertEquals(array('value'), $bag->get('foo', 'nope', false), 'assoc indices of multi-valued headers are ignored'); + } + + public function testContains() + { + $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz')); + $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value'); + $this->assertTrue($bag->contains('fuzz', 'bizz'), '->contains second value'); + $this->assertFalse($bag->contains('nope', 'nope'), '->contains unknown value'); + $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value'); + + // Multiple values + $bag->set('foo', 'bor', false); + $this->assertTrue($bag->contains('foo', 'bar'), '->contains first value'); + $this->assertTrue($bag->contains('foo', 'bor'), '->contains second value'); + $this->assertFalse($bag->contains('foo', 'nope'), '->contains unknown value'); + } + + public function testCacheControlDirectiveAccessors() + { + $bag = new HeaderBag(); + $bag->addCacheControlDirective('public'); + + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertTrue($bag->getCacheControlDirective('public')); + $this->assertEquals('public', $bag->get('cache-control')); + + $bag->addCacheControlDirective('max-age', 10); + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(10, $bag->getCacheControlDirective('max-age')); + $this->assertEquals('max-age=10, public', $bag->get('cache-control')); + + $bag->removeCacheControlDirective('max-age'); + $this->assertFalse($bag->hasCacheControlDirective('max-age')); + } + + public function testCacheControlDirectiveParsing() + { + $bag = new HeaderBag(array('cache-control' => 'public, max-age=10')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertTrue($bag->getCacheControlDirective('public')); + + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(10, $bag->getCacheControlDirective('max-age')); + + $bag->addCacheControlDirective('s-maxage', 100); + $this->assertEquals('max-age=10, public, s-maxage=100', $bag->get('cache-control')); + } + + public function testCacheControlDirectiveParsingQuotedZero() + { + $bag = new HeaderBag(array('cache-control' => 'max-age="0"')); + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(0, $bag->getCacheControlDirective('max-age')); + } + + public function testCacheControlDirectiveOverrideWithReplace() + { + $bag = new HeaderBag(array('cache-control' => 'private, max-age=100')); + $bag->replace(array('cache-control' => 'public, max-age=10')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertTrue($bag->getCacheControlDirective('public')); + + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(10, $bag->getCacheControlDirective('max-age')); + } + + public function testCacheControlClone() + { + $headers = array('foo' => 'bar'); + $bag1 = new HeaderBag($headers); + $bag2 = new HeaderBag($bag1->all()); + + $this->assertEquals($bag1->all(), $bag2->all()); + } + + public function testGetIterator() + { + $headers = array('foo' => 'bar', 'hello' => 'world', 'third' => 'charm'); + $headerBag = new HeaderBag($headers); + + $i = 0; + foreach ($headerBag as $key => $val) { + ++$i; + $this->assertEquals(array($headers[$key]), $val); + } + + $this->assertEquals(count($headers), $i); + } + + public function testCount() + { + $headers = array('foo' => 'bar', 'HELLO' => 'WORLD'); + $headerBag = new HeaderBag($headers); + + $this->assertEquals(count($headers), count($headerBag)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/IpUtilsTest.php b/vendor/symfony/http-foundation/Tests/IpUtilsTest.php new file mode 100644 index 00000000..297ee3d8 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/IpUtilsTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\IpUtils; + +class IpUtilsTest extends TestCase +{ + /** + * @dataProvider getIpv4Data + */ + public function testIpv4($matches, $remoteAddr, $cidr) + { + $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); + } + + public function getIpv4Data() + { + return array( + array(true, '192.168.1.1', '192.168.1.1'), + array(true, '192.168.1.1', '192.168.1.1/1'), + array(true, '192.168.1.1', '192.168.1.0/24'), + array(false, '192.168.1.1', '1.2.3.4/1'), + array(false, '192.168.1.1', '192.168.1.1/33'), // invalid subnet + array(true, '192.168.1.1', array('1.2.3.4/1', '192.168.1.0/24')), + array(true, '192.168.1.1', array('192.168.1.0/24', '1.2.3.4/1')), + array(false, '192.168.1.1', array('1.2.3.4/1', '4.3.2.1/1')), + array(true, '1.2.3.4', '0.0.0.0/0'), + array(true, '1.2.3.4', '192.168.1.0/0'), + array(false, '1.2.3.4', '256.256.256/0'), // invalid CIDR notation + array(false, 'an_invalid_ip', '192.168.1.0/24'), + ); + } + + /** + * @dataProvider getIpv6Data + */ + public function testIpv6($matches, $remoteAddr, $cidr) + { + if (!defined('AF_INET6')) { + $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".'); + } + + $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); + } + + public function getIpv6Data() + { + return array( + array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), + array(false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), + array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'), + array(true, '0:0:0:0:0:0:0:1', '::1'), + array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'), + array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '2a01:198:603:0::/65')), + array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('2a01:198:603:0::/65', '::1')), + array(false, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '1a01:198:603:0::/65')), + array(false, '}__test|O:21:"JDatabaseDriverMysqli":3:{s:2', '::1'), + array(false, '2a01:198:603:0:396e:4789:8e99:890f', 'unknown'), + ); + } + + /** + * @expectedException \RuntimeException + * @requires extension sockets + */ + public function testAnIpv6WithOptionDisabledIpv6() + { + if (defined('AF_INET6')) { + $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".'); + } + + IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/JsonResponseTest.php b/vendor/symfony/http-foundation/Tests/JsonResponseTest.php new file mode 100644 index 00000000..c8b93778 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/JsonResponseTest.php @@ -0,0 +1,253 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\JsonResponse; + +class JsonResponseTest extends TestCase +{ + public function testConstructorEmptyCreatesJsonObject() + { + $response = new JsonResponse(); + $this->assertSame('{}', $response->getContent()); + } + + public function testConstructorWithArrayCreatesJsonArray() + { + $response = new JsonResponse(array(0, 1, 2, 3)); + $this->assertSame('[0,1,2,3]', $response->getContent()); + } + + public function testConstructorWithAssocArrayCreatesJsonObject() + { + $response = new JsonResponse(array('foo' => 'bar')); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + public function testConstructorWithSimpleTypes() + { + $response = new JsonResponse('foo'); + $this->assertSame('"foo"', $response->getContent()); + + $response = new JsonResponse(0); + $this->assertSame('0', $response->getContent()); + + $response = new JsonResponse(0.1); + $this->assertSame('0.1', $response->getContent()); + + $response = new JsonResponse(true); + $this->assertSame('true', $response->getContent()); + } + + public function testConstructorWithCustomStatus() + { + $response = new JsonResponse(array(), 202); + $this->assertSame(202, $response->getStatusCode()); + } + + public function testConstructorAddsContentTypeHeader() + { + $response = new JsonResponse(); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + } + + public function testConstructorWithCustomHeaders() + { + $response = new JsonResponse(array(), 200, array('ETag' => 'foo')); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + $this->assertSame('foo', $response->headers->get('ETag')); + } + + public function testConstructorWithCustomContentType() + { + $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json'); + + $response = new JsonResponse(array(), 200, $headers); + $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type')); + } + + public function testSetJson() + { + $response = new JsonResponse('1', 200, array(), true); + $this->assertEquals('1', $response->getContent()); + + $response = new JsonResponse('[1]', 200, array(), true); + $this->assertEquals('[1]', $response->getContent()); + + $response = new JsonResponse(null, 200, array()); + $response->setJson('true'); + $this->assertEquals('true', $response->getContent()); + } + + public function testCreate() + { + $response = JsonResponse::create(array('foo' => 'bar'), 204); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertEquals('{"foo":"bar"}', $response->getContent()); + $this->assertEquals(204, $response->getStatusCode()); + } + + public function testStaticCreateEmptyJsonObject() + { + $response = JsonResponse::create(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('{}', $response->getContent()); + } + + public function testStaticCreateJsonArray() + { + $response = JsonResponse::create(array(0, 1, 2, 3)); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('[0,1,2,3]', $response->getContent()); + } + + public function testStaticCreateJsonObject() + { + $response = JsonResponse::create(array('foo' => 'bar')); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + public function testStaticCreateWithSimpleTypes() + { + $response = JsonResponse::create('foo'); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('"foo"', $response->getContent()); + + $response = JsonResponse::create(0); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('0', $response->getContent()); + + $response = JsonResponse::create(0.1); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('0.1', $response->getContent()); + + $response = JsonResponse::create(true); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('true', $response->getContent()); + } + + public function testStaticCreateWithCustomStatus() + { + $response = JsonResponse::create(array(), 202); + $this->assertSame(202, $response->getStatusCode()); + } + + public function testStaticCreateAddsContentTypeHeader() + { + $response = JsonResponse::create(); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + } + + public function testStaticCreateWithCustomHeaders() + { + $response = JsonResponse::create(array(), 200, array('ETag' => 'foo')); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + $this->assertSame('foo', $response->headers->get('ETag')); + } + + public function testStaticCreateWithCustomContentType() + { + $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json'); + + $response = JsonResponse::create(array(), 200, $headers); + $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type')); + } + + public function testSetCallback() + { + $response = JsonResponse::create(array('foo' => 'bar'))->setCallback('callback'); + + $this->assertEquals('/**/callback({"foo":"bar"});', $response->getContent()); + $this->assertEquals('text/javascript', $response->headers->get('Content-Type')); + } + + public function testJsonEncodeFlags() + { + $response = new JsonResponse('<>\'&"'); + + $this->assertEquals('"\u003C\u003E\u0027\u0026\u0022"', $response->getContent()); + } + + public function testGetEncodingOptions() + { + $response = new JsonResponse(); + + $this->assertEquals(JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT, $response->getEncodingOptions()); + } + + public function testSetEncodingOptions() + { + $response = new JsonResponse(); + $response->setData(array(array(1, 2, 3))); + + $this->assertEquals('[[1,2,3]]', $response->getContent()); + + $response->setEncodingOptions(JSON_FORCE_OBJECT); + + $this->assertEquals('{"0":{"0":1,"1":2,"2":3}}', $response->getContent()); + } + + public function testItAcceptsJsonAsString() + { + $response = JsonResponse::fromJsonString('{"foo":"bar"}'); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetCallbackInvalidIdentifier() + { + $response = new JsonResponse('foo'); + $response->setCallback('+invalid'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetContent() + { + JsonResponse::create("\xB1\x31"); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage This error is expected + */ + public function testSetContentJsonSerializeError() + { + $serializable = new JsonSerializableObject(); + + JsonResponse::create($serializable); + } + + public function testSetComplexCallback() + { + $response = JsonResponse::create(array('foo' => 'bar')); + $response->setCallback('ಠ_ಠ["foo"].bar[0]'); + + $this->assertEquals('/**/ಠ_ಠ["foo"].bar[0]({"foo":"bar"});', $response->getContent()); + } +} + +if (interface_exists('JsonSerializable')) { + class JsonSerializableObject implements \JsonSerializable + { + public function jsonSerialize() + { + throw new \Exception('This error is expected'); + } + } +} diff --git a/vendor/symfony/http-foundation/Tests/ParameterBagTest.php b/vendor/symfony/http-foundation/Tests/ParameterBagTest.php new file mode 100644 index 00000000..5311a0d8 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ParameterBagTest.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\ParameterBag; + +class ParameterBagTest extends TestCase +{ + public function testConstructor() + { + $this->testAll(); + } + + public function testAll() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $bag->all(), '->all() gets all the input'); + } + + public function testKeys() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $this->assertEquals(array('foo'), $bag->keys()); + } + + public function testAdd() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $bag->add(array('bar' => 'bas')); + $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all()); + } + + public function testRemove() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $bag->add(array('bar' => 'bas')); + $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all()); + $bag->remove('bar'); + $this->assertEquals(array('foo' => 'bar'), $bag->all()); + } + + public function testReplace() + { + $bag = new ParameterBag(array('foo' => 'bar')); + + $bag->replace(array('FOO' => 'BAR')); + $this->assertEquals(array('FOO' => 'BAR'), $bag->all(), '->replace() replaces the input with the argument'); + $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input'); + } + + public function testGet() + { + $bag = new ParameterBag(array('foo' => 'bar', 'null' => null)); + + $this->assertEquals('bar', $bag->get('foo'), '->get() gets the value of a parameter'); + $this->assertEquals('default', $bag->get('unknown', 'default'), '->get() returns second argument as default if a parameter is not defined'); + $this->assertNull($bag->get('null', 'default'), '->get() returns null if null is set'); + } + + public function testGetDoesNotUseDeepByDefault() + { + $bag = new ParameterBag(array('foo' => array('bar' => 'moo'))); + + $this->assertNull($bag->get('foo[bar]')); + } + + public function testSet() + { + $bag = new ParameterBag(array()); + + $bag->set('foo', 'bar'); + $this->assertEquals('bar', $bag->get('foo'), '->set() sets the value of parameter'); + + $bag->set('foo', 'baz'); + $this->assertEquals('baz', $bag->get('foo'), '->set() overrides previously set parameter'); + } + + public function testHas() + { + $bag = new ParameterBag(array('foo' => 'bar')); + + $this->assertTrue($bag->has('foo'), '->has() returns true if a parameter is defined'); + $this->assertFalse($bag->has('unknown'), '->has() return false if a parameter is not defined'); + } + + public function testGetAlpha() + { + $bag = new ParameterBag(array('word' => 'foo_BAR_012')); + + $this->assertEquals('fooBAR', $bag->getAlpha('word'), '->getAlpha() gets only alphabetic characters'); + $this->assertEquals('', $bag->getAlpha('unknown'), '->getAlpha() returns empty string if a parameter is not defined'); + } + + public function testGetAlnum() + { + $bag = new ParameterBag(array('word' => 'foo_BAR_012')); + + $this->assertEquals('fooBAR012', $bag->getAlnum('word'), '->getAlnum() gets only alphanumeric characters'); + $this->assertEquals('', $bag->getAlnum('unknown'), '->getAlnum() returns empty string if a parameter is not defined'); + } + + public function testGetDigits() + { + $bag = new ParameterBag(array('word' => 'foo_BAR_012')); + + $this->assertEquals('012', $bag->getDigits('word'), '->getDigits() gets only digits as string'); + $this->assertEquals('', $bag->getDigits('unknown'), '->getDigits() returns empty string if a parameter is not defined'); + } + + public function testGetInt() + { + $bag = new ParameterBag(array('digits' => '0123')); + + $this->assertEquals(123, $bag->getInt('digits'), '->getInt() gets a value of parameter as integer'); + $this->assertEquals(0, $bag->getInt('unknown'), '->getInt() returns zero if a parameter is not defined'); + } + + public function testFilter() + { + $bag = new ParameterBag(array( + 'digits' => '0123ab', + 'email' => 'example@example.com', + 'url' => 'http://example.com/foo', + 'dec' => '256', + 'hex' => '0x100', + 'array' => array('bang'), + )); + + $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found'); + + $this->assertEquals('0123', $bag->filter('digits', '', FILTER_SANITIZE_NUMBER_INT), '->filter() gets a value of parameter as integer filtering out invalid characters'); + + $this->assertEquals('example@example.com', $bag->filter('email', '', FILTER_VALIDATE_EMAIL), '->filter() gets a value of parameter as email'); + + $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, array('flags' => FILTER_FLAG_PATH_REQUIRED)), '->filter() gets a value of parameter as URL with a path'); + + // This test is repeated for code-coverage + $this->assertEquals('http://example.com/foo', $bag->filter('url', '', FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED), '->filter() gets a value of parameter as URL with a path'); + + $this->assertFalse($bag->filter('dec', '', FILTER_VALIDATE_INT, array( + 'flags' => FILTER_FLAG_ALLOW_HEX, + 'options' => array('min_range' => 1, 'max_range' => 0xff), + )), '->filter() gets a value of parameter as integer between boundaries'); + + $this->assertFalse($bag->filter('hex', '', FILTER_VALIDATE_INT, array( + 'flags' => FILTER_FLAG_ALLOW_HEX, + 'options' => array('min_range' => 1, 'max_range' => 0xff), + )), '->filter() gets a value of parameter as integer between boundaries'); + + $this->assertEquals(array('bang'), $bag->filter('array', ''), '->filter() gets a value of parameter as an array'); + } + + public function testGetIterator() + { + $parameters = array('foo' => 'bar', 'hello' => 'world'); + $bag = new ParameterBag($parameters); + + $i = 0; + foreach ($bag as $key => $val) { + ++$i; + $this->assertEquals($parameters[$key], $val); + } + + $this->assertEquals(count($parameters), $i); + } + + public function testCount() + { + $parameters = array('foo' => 'bar', 'hello' => 'world'); + $bag = new ParameterBag($parameters); + + $this->assertEquals(count($parameters), count($bag)); + } + + public function testGetBoolean() + { + $parameters = array('string_true' => 'true', 'string_false' => 'false'); + $bag = new ParameterBag($parameters); + + $this->assertTrue($bag->getBoolean('string_true'), '->getBoolean() gets the string true as boolean true'); + $this->assertFalse($bag->getBoolean('string_false'), '->getBoolean() gets the string false as boolean false'); + $this->assertFalse($bag->getBoolean('unknown'), '->getBoolean() returns false if a parameter is not defined'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php b/vendor/symfony/http-foundation/Tests/RedirectResponseTest.php new file mode 100644 index 00000000..d389e83d --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RedirectResponseTest.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\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\RedirectResponse; + +class RedirectResponseTest extends TestCase +{ + public function testGenerateMetaRedirect() + { + $response = new RedirectResponse('foo.bar'); + + $this->assertEquals(1, preg_match( + '##', + preg_replace(array('/\s+/', '/\'/'), array(' ', '"'), $response->getContent()) + )); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRedirectResponseConstructorNullUrl() + { + $response = new RedirectResponse(null); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRedirectResponseConstructorWrongStatusCode() + { + $response = new RedirectResponse('foo.bar', 404); + } + + public function testGenerateLocationHeader() + { + $response = new RedirectResponse('foo.bar'); + + $this->assertTrue($response->headers->has('Location')); + $this->assertEquals('foo.bar', $response->headers->get('Location')); + } + + public function testGetTargetUrl() + { + $response = new RedirectResponse('foo.bar'); + + $this->assertEquals('foo.bar', $response->getTargetUrl()); + } + + public function testSetTargetUrl() + { + $response = new RedirectResponse('foo.bar'); + $response->setTargetUrl('baz.beep'); + + $this->assertEquals('baz.beep', $response->getTargetUrl()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetTargetUrlNull() + { + $response = new RedirectResponse('foo.bar'); + $response->setTargetUrl(null); + } + + public function testCreate() + { + $response = RedirectResponse::create('foo', 301); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response); + $this->assertEquals(301, $response->getStatusCode()); + } + + public function testCacheHeaders() + { + $response = new RedirectResponse('foo.bar', 301); + $this->assertFalse($response->headers->hasCacheControlDirective('no-cache')); + + $response = new RedirectResponse('foo.bar', 301, array('cache-control' => 'max-age=86400')); + $this->assertFalse($response->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($response->headers->hasCacheControlDirective('max-age')); + + $response = new RedirectResponse('foo.bar', 302); + $this->assertTrue($response->headers->hasCacheControlDirective('no-cache')); + } +} diff --git a/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php new file mode 100644 index 00000000..b5d80048 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\Request; + +class RequestMatcherTest extends TestCase +{ + /** + * @dataProvider getMethodData + */ + public function testMethod($requestMethod, $matcherMethod, $isMatch) + { + $matcher = new RequestMatcher(); + $matcher->matchMethod($matcherMethod); + $request = Request::create('', $requestMethod); + $this->assertSame($isMatch, $matcher->matches($request)); + + $matcher = new RequestMatcher(null, null, $matcherMethod); + $request = Request::create('', $requestMethod); + $this->assertSame($isMatch, $matcher->matches($request)); + } + + public function getMethodData() + { + return array( + array('get', 'get', true), + array('get', array('get', 'post'), true), + array('get', 'post', false), + array('get', 'GET', true), + array('get', array('GET', 'POST'), true), + array('get', 'POST', false), + ); + } + + public function testScheme() + { + $httpRequest = $request = $request = Request::create(''); + $httpsRequest = $request = $request = Request::create('', 'get', array(), array(), array(), array('HTTPS' => 'on')); + + $matcher = new RequestMatcher(); + $matcher->matchScheme('https'); + $this->assertFalse($matcher->matches($httpRequest)); + $this->assertTrue($matcher->matches($httpsRequest)); + + $matcher->matchScheme('http'); + $this->assertFalse($matcher->matches($httpsRequest)); + $this->assertTrue($matcher->matches($httpRequest)); + + $matcher = new RequestMatcher(); + $this->assertTrue($matcher->matches($httpsRequest)); + $this->assertTrue($matcher->matches($httpRequest)); + } + + /** + * @dataProvider getHostData + */ + public function testHost($pattern, $isMatch) + { + $matcher = new RequestMatcher(); + $request = Request::create('', 'get', array(), array(), array(), array('HTTP_HOST' => 'foo.example.com')); + + $matcher->matchHost($pattern); + $this->assertSame($isMatch, $matcher->matches($request)); + + $matcher = new RequestMatcher(null, $pattern); + $this->assertSame($isMatch, $matcher->matches($request)); + } + + public function getHostData() + { + return array( + array('.*\.example\.com', true), + array('\.example\.com$', true), + array('^.*\.example\.com$', true), + array('.*\.sensio\.com', false), + array('.*\.example\.COM', true), + array('\.example\.COM$', true), + array('^.*\.example\.COM$', true), + array('.*\.sensio\.COM', false), + ); + } + + public function testPath() + { + $matcher = new RequestMatcher(); + + $request = Request::create('/admin/foo'); + + $matcher->matchPath('/admin/.*'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchPath('/admin'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchPath('^/admin/.*$'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchMethod('/blog/.*'); + $this->assertFalse($matcher->matches($request)); + } + + public function testPathWithLocaleIsNotSupported() + { + $matcher = new RequestMatcher(); + $request = Request::create('/en/login'); + $request->setLocale('en'); + + $matcher->matchPath('^/{_locale}/login$'); + $this->assertFalse($matcher->matches($request)); + } + + public function testPathWithEncodedCharacters() + { + $matcher = new RequestMatcher(); + $request = Request::create('/admin/fo%20o'); + $matcher->matchPath('^/admin/fo o*$'); + $this->assertTrue($matcher->matches($request)); + } + + public function testAttributes() + { + $matcher = new RequestMatcher(); + + $request = Request::create('/admin/foo'); + $request->attributes->set('foo', 'foo_bar'); + + $matcher->matchAttribute('foo', 'foo_.*'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchAttribute('foo', 'foo'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchAttribute('foo', '^foo_bar$'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchAttribute('foo', 'babar'); + $this->assertFalse($matcher->matches($request)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/RequestStackTest.php b/vendor/symfony/http-foundation/Tests/RequestStackTest.php new file mode 100644 index 00000000..a84fb26f --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RequestStackTest.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\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +class RequestStackTest extends TestCase +{ + public function testGetCurrentRequest() + { + $requestStack = new RequestStack(); + $this->assertNull($requestStack->getCurrentRequest()); + + $request = Request::create('/foo'); + + $requestStack->push($request); + $this->assertSame($request, $requestStack->getCurrentRequest()); + + $this->assertSame($request, $requestStack->pop()); + $this->assertNull($requestStack->getCurrentRequest()); + + $this->assertNull($requestStack->pop()); + } + + public function testGetMasterRequest() + { + $requestStack = new RequestStack(); + $this->assertNull($requestStack->getMasterRequest()); + + $masterRequest = Request::create('/foo'); + $subRequest = Request::create('/bar'); + + $requestStack->push($masterRequest); + $requestStack->push($subRequest); + + $this->assertSame($masterRequest, $requestStack->getMasterRequest()); + } + + public function testGetParentRequest() + { + $requestStack = new RequestStack(); + $this->assertNull($requestStack->getParentRequest()); + + $masterRequest = Request::create('/foo'); + + $requestStack->push($masterRequest); + $this->assertNull($requestStack->getParentRequest()); + + $firstSubRequest = Request::create('/bar'); + + $requestStack->push($firstSubRequest); + $this->assertSame($masterRequest, $requestStack->getParentRequest()); + + $secondSubRequest = Request::create('/baz'); + + $requestStack->push($secondSubRequest); + $this->assertSame($firstSubRequest, $requestStack->getParentRequest()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/RequestTest.php b/vendor/symfony/http-foundation/Tests/RequestTest.php new file mode 100644 index 00000000..b36fbb7e --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RequestTest.php @@ -0,0 +1,2207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Request; + +class RequestTest extends TestCase +{ + protected function tearDown() + { + // reset + Request::setTrustedProxies(array(), -1); + } + + public function testInitialize() + { + $request = new Request(); + + $request->initialize(array('foo' => 'bar')); + $this->assertEquals('bar', $request->query->get('foo'), '->initialize() takes an array of query parameters as its first argument'); + + $request->initialize(array(), array('foo' => 'bar')); + $this->assertEquals('bar', $request->request->get('foo'), '->initialize() takes an array of request parameters as its second argument'); + + $request->initialize(array(), array(), array('foo' => 'bar')); + $this->assertEquals('bar', $request->attributes->get('foo'), '->initialize() takes an array of attributes as its third argument'); + + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_FOO' => 'bar')); + $this->assertEquals('bar', $request->headers->get('FOO'), '->initialize() takes an array of HTTP headers as its sixth argument'); + } + + public function testGetLocale() + { + $request = new Request(); + $request->setLocale('pl'); + $locale = $request->getLocale(); + $this->assertEquals('pl', $locale); + } + + public function testGetUser() + { + $request = Request::create('http://user_test:password_test@test.com/'); + $user = $request->getUser(); + + $this->assertEquals('user_test', $user); + } + + public function testGetPassword() + { + $request = Request::create('http://user_test:password_test@test.com/'); + $password = $request->getPassword(); + + $this->assertEquals('password_test', $password); + } + + public function testIsNoCache() + { + $request = new Request(); + $isNoCache = $request->isNoCache(); + + $this->assertFalse($isNoCache); + } + + public function testGetContentType() + { + $request = new Request(); + $contentType = $request->getContentType(); + + $this->assertNull($contentType); + } + + public function testSetDefaultLocale() + { + $request = new Request(); + $request->setDefaultLocale('pl'); + $locale = $request->getLocale(); + + $this->assertEquals('pl', $locale); + } + + public function testCreate() + { + $request = Request::create('http://test.com/foo?bar=baz'); + $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com/foo', 'GET', array('bar' => 'baz')); + $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com/foo?bar=foo', 'GET', array('bar' => 'baz')); + $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('https://test.com/foo?bar=baz'); + $this->assertEquals('https://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(443, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('test.com:90/foo'); + $this->assertEquals('http://test.com:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('https://test.com:90/foo'); + $this->assertEquals('https://test.com:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('https://127.0.0.1:90/foo'); + $this->assertEquals('https://127.0.0.1:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('127.0.0.1', $request->getHost()); + $this->assertEquals('127.0.0.1:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('https://[::1]:90/foo'); + $this->assertEquals('https://[::1]:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('[::1]', $request->getHost()); + $this->assertEquals('[::1]:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('https://[::1]/foo'); + $this->assertEquals('https://[::1]/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('[::1]', $request->getHost()); + $this->assertEquals('[::1]', $request->getHttpHost()); + $this->assertEquals(443, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; + $request = Request::create('http://example.com/jsonrpc', 'POST', array(), array(), array(), array(), $json); + $this->assertEquals($json, $request->getContent()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com'); + $this->assertEquals('http://test.com/', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com?test=1'); + $this->assertEquals('http://test.com/?test=1', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('test=1', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com:90/?test=1'); + $this->assertEquals('http://test.com:90/?test=1', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('test=1', $request->getQueryString()); + $this->assertEquals(90, $request->getPort()); + $this->assertEquals('test.com:90', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://username:password@test.com'); + $this->assertEquals('http://test.com/', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertEquals('username', $request->getUser()); + $this->assertEquals('password', $request->getPassword()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://username@test.com'); + $this->assertEquals('http://test.com/', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertEquals('username', $request->getUser()); + $this->assertSame('', $request->getPassword()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com/?foo'); + $this->assertEquals('/?foo', $request->getRequestUri()); + $this->assertEquals(array('foo' => ''), $request->query->all()); + + // assume rewrite rule: (.*) --> app/app.php; app/ is a symlink to a symfony web/ directory + $request = Request::create('http://test.com/apparthotel-1234', 'GET', array(), array(), array(), + array( + 'DOCUMENT_ROOT' => '/var/www/www.test.com', + 'SCRIPT_FILENAME' => '/var/www/www.test.com/app/app.php', + 'SCRIPT_NAME' => '/app/app.php', + 'PHP_SELF' => '/app/app.php/apparthotel-1234', + )); + $this->assertEquals('http://test.com/apparthotel-1234', $request->getUri()); + $this->assertEquals('/apparthotel-1234', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + } + + public function testCreateCheckPrecedence() + { + // server is used by default + $request = Request::create('/', 'DELETE', array(), array(), array(), array( + 'HTTP_HOST' => 'example.com', + 'HTTPS' => 'on', + 'SERVER_PORT' => 443, + 'PHP_AUTH_USER' => 'fabien', + 'PHP_AUTH_PW' => 'pa$$', + 'QUERY_STRING' => 'foo=bar', + 'CONTENT_TYPE' => 'application/json', + )); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(443, $request->getPort()); + $this->assertTrue($request->isSecure()); + $this->assertEquals('fabien', $request->getUser()); + $this->assertEquals('pa$$', $request->getPassword()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals('application/json', $request->headers->get('CONTENT_TYPE')); + + // URI has precedence over server + $request = Request::create('http://thomas:pokemon@example.net:8080/?foo=bar', 'GET', array(), array(), array(), array( + 'HTTP_HOST' => 'example.com', + 'HTTPS' => 'on', + 'SERVER_PORT' => 443, + )); + $this->assertEquals('example.net', $request->getHost()); + $this->assertEquals(8080, $request->getPort()); + $this->assertFalse($request->isSecure()); + $this->assertEquals('thomas', $request->getUser()); + $this->assertEquals('pokemon', $request->getPassword()); + $this->assertEquals('foo=bar', $request->getQueryString()); + } + + public function testDuplicate() + { + $request = new Request(array('foo' => 'bar'), array('foo' => 'bar'), array('foo' => 'bar'), array(), array(), array('HTTP_FOO' => 'bar')); + $dup = $request->duplicate(); + + $this->assertEquals($request->query->all(), $dup->query->all(), '->duplicate() duplicates a request an copy the current query parameters'); + $this->assertEquals($request->request->all(), $dup->request->all(), '->duplicate() duplicates a request an copy the current request parameters'); + $this->assertEquals($request->attributes->all(), $dup->attributes->all(), '->duplicate() duplicates a request an copy the current attributes'); + $this->assertEquals($request->headers->all(), $dup->headers->all(), '->duplicate() duplicates a request an copy the current HTTP headers'); + + $dup = $request->duplicate(array('foo' => 'foobar'), array('foo' => 'foobar'), array('foo' => 'foobar'), array(), array(), array('HTTP_FOO' => 'foobar')); + + $this->assertEquals(array('foo' => 'foobar'), $dup->query->all(), '->duplicate() overrides the query parameters if provided'); + $this->assertEquals(array('foo' => 'foobar'), $dup->request->all(), '->duplicate() overrides the request parameters if provided'); + $this->assertEquals(array('foo' => 'foobar'), $dup->attributes->all(), '->duplicate() overrides the attributes if provided'); + $this->assertEquals(array('foo' => array('foobar')), $dup->headers->all(), '->duplicate() overrides the HTTP header if provided'); + } + + public function testDuplicateWithFormat() + { + $request = new Request(array(), array(), array('_format' => 'json')); + $dup = $request->duplicate(); + + $this->assertEquals('json', $dup->getRequestFormat()); + $this->assertEquals('json', $dup->attributes->get('_format')); + + $request = new Request(); + $request->setRequestFormat('xml'); + $dup = $request->duplicate(); + + $this->assertEquals('xml', $dup->getRequestFormat()); + } + + /** + * @dataProvider getFormatToMimeTypeMapProviderWithAdditionalNullFormat + */ + public function testGetFormatFromMimeType($format, $mimeTypes) + { + $request = new Request(); + foreach ($mimeTypes as $mime) { + $this->assertEquals($format, $request->getFormat($mime)); + } + $request->setFormat($format, $mimeTypes); + foreach ($mimeTypes as $mime) { + $this->assertEquals($format, $request->getFormat($mime)); + + if (null !== $format) { + $this->assertEquals($mimeTypes[0], $request->getMimeType($format)); + } + } + } + + public function getFormatToMimeTypeMapProviderWithAdditionalNullFormat() + { + return array_merge( + array(array(null, array(null, 'unexistent-mime-type'))), + $this->getFormatToMimeTypeMapProvider() + ); + } + + public function testGetFormatFromMimeTypeWithParameters() + { + $request = new Request(); + $this->assertEquals('json', $request->getFormat('application/json; charset=utf-8')); + } + + /** + * @dataProvider getFormatToMimeTypeMapProvider + */ + public function testGetMimeTypeFromFormat($format, $mimeTypes) + { + $request = new Request(); + $this->assertEquals($mimeTypes[0], $request->getMimeType($format)); + } + + /** + * @dataProvider getFormatToMimeTypeMapProvider + */ + public function testGetMimeTypesFromFormat($format, $mimeTypes) + { + $this->assertEquals($mimeTypes, Request::getMimeTypes($format)); + } + + public function testGetMimeTypesFromInexistentFormat() + { + $request = new Request(); + $this->assertNull($request->getMimeType('foo')); + $this->assertEquals(array(), Request::getMimeTypes('foo')); + } + + public function testGetFormatWithCustomMimeType() + { + $request = new Request(); + $request->setFormat('custom', 'application/vnd.foo.api;myversion=2.3'); + $this->assertEquals('custom', $request->getFormat('application/vnd.foo.api;myversion=2.3')); + } + + public function getFormatToMimeTypeMapProvider() + { + return array( + array('txt', array('text/plain')), + array('js', array('application/javascript', 'application/x-javascript', 'text/javascript')), + array('css', array('text/css')), + array('json', array('application/json', 'application/x-json')), + array('xml', array('text/xml', 'application/xml', 'application/x-xml')), + array('rdf', array('application/rdf+xml')), + array('atom', array('application/atom+xml')), + ); + } + + public function testGetUri() + { + $server = array(); + + // Standard Request on non default PORT + // http://host:8080/index.php/path/info?query=string + + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/index.php/path/info?query=string'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PATH_INFO'] = '/path/info'; + $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info'; + $server['PHP_SELF'] = '/index_dev.php/path/info'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request = new Request(); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host:8080/index.php/path/info?query=string', $request->getUri(), '->getUri() with non default port'); + + // Use std port number + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port without HOST_HEADER'); + + // Request with URL REWRITING (hide index.php) + // RewriteCond %{REQUEST_FILENAME} !-f + // RewriteRule ^(.*)$ index.php [QSA,L] + // http://host:8080/path/info?query=string + $server = array(); + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['REDIRECT_QUERY_STRING'] = 'query=string'; + $server['REDIRECT_URL'] = '/path/info'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/path/info?toto=test&1=1'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PHP_SELF'] = '/index.php'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/path/info?query=string', $request->getUri(), '->getUri() with rewrite'); + + // Use std port number + // http://host/path/info?query=string + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/path/info?query=string', $request->getUri(), '->getUri() with rewrite and default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/path/info?query=string', $request->getUri(), '->getUri() with rewrite, default port without HOST_HEADER'); + + // With encoded characters + + $server = array( + 'HTTP_HOST' => 'host:8080', + 'SERVER_NAME' => 'servername', + 'SERVER_PORT' => '8080', + 'QUERY_STRING' => 'query=string', + 'REQUEST_URI' => '/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', + 'SCRIPT_NAME' => '/ba se/index_dev.php', + 'PATH_TRANSLATED' => 'redirect:/index.php/foo bar/in+fo', + 'PHP_SELF' => '/ba se/index_dev.php/path/info', + 'SCRIPT_FILENAME' => '/some/where/ba se/index_dev.php', + ); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals( + 'http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', + $request->getUri() + ); + + // with user info + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri()); + + $server['PHP_AUTH_PW'] = 'symfony'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri()); + } + + public function testGetUriForPath() + { + $request = Request::create('http://test.com/foo?bar=baz'); + $this->assertEquals('http://test.com/some/path', $request->getUriForPath('/some/path')); + + $request = Request::create('http://test.com:90/foo?bar=baz'); + $this->assertEquals('http://test.com:90/some/path', $request->getUriForPath('/some/path')); + + $request = Request::create('https://test.com/foo?bar=baz'); + $this->assertEquals('https://test.com/some/path', $request->getUriForPath('/some/path')); + + $request = Request::create('https://test.com:90/foo?bar=baz'); + $this->assertEquals('https://test.com:90/some/path', $request->getUriForPath('/some/path')); + + $server = array(); + + // Standard Request on non default PORT + // http://host:8080/index.php/path/info?query=string + + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/index.php/path/info?query=string'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PATH_INFO'] = '/path/info'; + $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info'; + $server['PHP_SELF'] = '/index_dev.php/path/info'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request = new Request(); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host:8080/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with non default port'); + + // Use std port number + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port without HOST_HEADER'); + + // Request with URL REWRITING (hide index.php) + // RewriteCond %{REQUEST_FILENAME} !-f + // RewriteRule ^(.*)$ index.php [QSA,L] + // http://host:8080/path/info?query=string + $server = array(); + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['REDIRECT_QUERY_STRING'] = 'query=string'; + $server['REDIRECT_URL'] = '/path/info'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/path/info?toto=test&1=1'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PHP_SELF'] = '/index.php'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/some/path', $request->getUriForPath('/some/path'), '->getUri() with rewrite'); + + // Use std port number + // http://host/path/info?query=string + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite and default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite, default port without HOST_HEADER'); + $this->assertEquals('servername', $request->getHttpHost()); + + // with user info + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path')); + + $server['PHP_AUTH_PW'] = 'symfony'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path')); + } + + /** + * @dataProvider getRelativeUriForPathData() + */ + public function testGetRelativeUriForPath($expected, $pathinfo, $path) + { + $this->assertEquals($expected, Request::create($pathinfo)->getRelativeUriForPath($path)); + } + + public function getRelativeUriForPathData() + { + return array( + array('me.png', '/foo', '/me.png'), + array('../me.png', '/foo/bar', '/me.png'), + array('me.png', '/foo/bar', '/foo/me.png'), + array('../baz/me.png', '/foo/bar/b', '/foo/baz/me.png'), + array('../../fooz/baz/me.png', '/foo/bar/b', '/fooz/baz/me.png'), + array('baz/me.png', '/foo/bar/b', 'baz/me.png'), + ); + } + + public function testGetUserInfo() + { + $request = new Request(); + + $server = array('PHP_AUTH_USER' => 'fabien'); + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('fabien', $request->getUserInfo()); + + $server['PHP_AUTH_USER'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('0', $request->getUserInfo()); + + $server['PHP_AUTH_PW'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('0:0', $request->getUserInfo()); + } + + public function testGetSchemeAndHttpHost() + { + $request = new Request(); + + $server = array(); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '90'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + + $server['PHP_AUTH_USER'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + + $server['PHP_AUTH_PW'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + } + + /** + * @dataProvider getQueryStringNormalizationData + */ + public function testGetQueryString($query, $expectedQuery, $msg) + { + $request = new Request(); + + $request->server->set('QUERY_STRING', $query); + $this->assertSame($expectedQuery, $request->getQueryString(), $msg); + } + + public function getQueryStringNormalizationData() + { + return array( + array('foo', 'foo', 'works with valueless parameters'), + array('foo=', 'foo=', 'includes a dangling equal sign'), + array('bar=&foo=bar', 'bar=&foo=bar', '->works with empty parameters'), + array('foo=bar&bar=', 'bar=&foo=bar', 'sorts keys alphabetically'), + + // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded). + // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. + array('him=John%20Doe&her=Jane+Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes spaces in both encodings "%20" and "+"'), + + array('foo[]=1&foo[]=2', 'foo%5B%5D=1&foo%5B%5D=2', 'allows array notation'), + array('foo=1&foo=2', 'foo=1&foo=2', 'allows repeated parameters'), + array('pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'), + array('0', '0', 'allows "0"'), + array('Jane Doe&John%20Doe', 'Jane%20Doe&John%20Doe', 'normalizes encoding in keys'), + array('her=Jane Doe&him=John%20Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes encoding in values'), + array('foo=bar&&&test&&', 'foo=bar&test', 'removes unneeded delimiters'), + array('formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'), + + // Ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway. + // PHP also does not include them when building _GET. + array('foo=bar&=a=b&=x=y', 'foo=bar', 'removes params with empty key'), + ); + } + + public function testGetQueryStringReturnsNull() + { + $request = new Request(); + + $this->assertNull($request->getQueryString(), '->getQueryString() returns null for non-existent query string'); + + $request->server->set('QUERY_STRING', ''); + $this->assertNull($request->getQueryString(), '->getQueryString() returns null for empty query string'); + } + + public function testGetHost() + { + $request = new Request(); + + $request->initialize(array('foo' => 'bar')); + $this->assertEquals('', $request->getHost(), '->getHost() return empty string if not initialized'); + + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.example.com')); + $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header'); + + // Host header with port number + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.example.com:8080')); + $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from Host Header with port number'); + + // Server values + $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.example.com')); + $this->assertEquals('www.example.com', $request->getHost(), '->getHost() from server name'); + + $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.example.com', 'HTTP_HOST' => 'www.host.com')); + $this->assertEquals('www.host.com', $request->getHost(), '->getHost() value from Host header has priority over SERVER_NAME '); + } + + public function testGetPort() + { + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'https', + 'HTTP_X_FORWARDED_PORT' => '443', + )); + $port = $request->getPort(); + + $this->assertEquals(80, $port, 'Without trusted proxies FORWARDED_PROTO and FORWARDED_PORT are ignored.'); + + Request::setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_ALL); + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'https', + 'HTTP_X_FORWARDED_PORT' => '8443', + )); + $this->assertEquals(80, $request->getPort(), 'With PROTO and PORT on untrusted connection server value takes precedence.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(8443, $request->getPort(), 'With PROTO and PORT set PORT takes precedence.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'https', + )); + $this->assertEquals(80, $request->getPort(), 'With only PROTO set getPort() ignores trusted headers on untrusted connection.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(443, $request->getPort(), 'With only PROTO set getPort() defaults to 443.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'http', + )); + $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() ignores trusted headers on untrusted connection.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(80, $request->getPort(), 'If X_FORWARDED_PROTO is set to HTTP getPort() returns port of the original request.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'On', + )); + $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is On, getPort() ignores trusted headers on untrusted connection.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is On, getPort() defaults to 443.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => '1', + )); + $this->assertEquals(80, $request->getPort(), 'With only PROTO set and value is 1, getPort() ignores trusted headers on untrusted connection.'); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertEquals(443, $request->getPort(), 'With only PROTO set and value is 1, getPort() defaults to 443.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'something-else', + )); + $port = $request->getPort(); + $this->assertEquals(80, $port, 'With only PROTO set and value is not recognized, getPort() defaults to 80.'); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetHostWithFakeHttpHostValue() + { + $request = new Request(); + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.host.com?query=string')); + $request->getHost(); + } + + public function testGetSetMethod() + { + $request = new Request(); + + $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns GET if no method is defined'); + + $request->setMethod('get'); + $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns an uppercased string'); + + $request->setMethod('PURGE'); + $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method even if it is not a standard one'); + + $request->setMethod('POST'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() returns the method POST if no _method is defined'); + + $request->setMethod('POST'); + $request->request->set('_method', 'purge'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled'); + + $request = new Request(); + $request->setMethod('POST'); + $request->request->set('_method', 'purge'); + + $this->assertFalse(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be disabled by default'); + + Request::enableHttpMethodParameterOverride(); + + $this->assertTrue(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be enabled now but it is not'); + + $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); + $this->disableHttpMethodParameterOverride(); + + $request = new Request(); + $request->setMethod('POST'); + $request->query->set('_method', 'purge'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled'); + + $request = new Request(); + $request->setMethod('POST'); + $request->query->set('_method', 'purge'); + Request::enableHttpMethodParameterOverride(); + $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); + $this->disableHttpMethodParameterOverride(); + + $request = new Request(); + $request->setMethod('POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete'); + $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override even though _method is set if defined and POST'); + + $request = new Request(); + $request->setMethod('POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete'); + $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override if defined and POST'); + } + + /** + * @dataProvider getClientIpsProvider + */ + public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); + + $this->assertEquals($expected[0], $request->getClientIp()); + } + + /** + * @dataProvider getClientIpsProvider + */ + public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); + + $this->assertEquals($expected, $request->getClientIps()); + } + + /** + * @dataProvider getClientIpsForwardedProvider + */ + public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies); + + $this->assertEquals($expected, $request->getClientIps()); + } + + public function getClientIpsForwardedProvider() + { + // $expected $remoteAddr $httpForwarded $trustedProxies + return array( + array(array('127.0.0.1'), '127.0.0.1', 'for="_gazonk"', null), + array(array('127.0.0.1'), '127.0.0.1', 'for="_gazonk"', array('127.0.0.1')), + array(array('88.88.88.88'), '127.0.0.1', 'for="88.88.88.88:80"', array('127.0.0.1')), + array(array('192.0.2.60'), '::1', 'for=192.0.2.60;proto=http;by=203.0.113.43', array('::1')), + array(array('2620:0:1cfe:face:b00c::3', '192.0.2.43'), '::1', 'for=192.0.2.43, for=2620:0:1cfe:face:b00c::3', array('::1')), + array(array('2001:db8:cafe::17'), '::1', 'for="[2001:db8:cafe::17]:4711', array('::1')), + ); + } + + public function getClientIpsProvider() + { + // $expected $remoteAddr $httpForwardedFor $trustedProxies + return array( + // simple IPv4 + array(array('88.88.88.88'), '88.88.88.88', null, null), + // trust the IPv4 remote addr + array(array('88.88.88.88'), '88.88.88.88', null, array('88.88.88.88')), + + // simple IPv6 + array(array('::1'), '::1', null, null), + // trust the IPv6 remote addr + array(array('::1'), '::1', null, array('::1')), + + // forwarded for with remote IPv4 addr not trusted + array(array('127.0.0.1'), '127.0.0.1', '88.88.88.88', null), + // forwarded for with remote IPv4 addr trusted + array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1')), + // forwarded for with remote IPv4 and all FF addrs trusted + array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1', '88.88.88.88')), + // forwarded for with remote IPv4 range trusted + array(array('88.88.88.88'), '123.45.67.89', '88.88.88.88', array('123.45.67.0/24')), + + // forwarded for with remote IPv6 addr not trusted + array(array('1620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', null), + // forwarded for with remote IPv6 addr trusted + array(array('2620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3')), + // forwarded for with remote IPv6 range trusted + array(array('88.88.88.88'), '2a01:198:603:0:396e:4789:8e99:890f', '88.88.88.88', array('2a01:198:603:0::/65')), + + // multiple forwarded for with remote IPv4 addr trusted + array(array('88.88.88.88', '87.65.43.21', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89')), + // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted + array(array('87.65.43.21', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '88.88.88.88')), + // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle + array(array('88.88.88.88', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21')), + // multiple forwarded for with remote IPv4 addr and all reverse proxies trusted + array(array('127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21', '88.88.88.88', '127.0.0.1')), + + // multiple forwarded for with remote IPv6 addr trusted + array(array('2620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3')), + // multiple forwarded for with remote IPv6 addr and some reverse proxies trusted + array(array('3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3')), + // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle + array(array('2620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3,3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3')), + + // client IP with port + array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88:12345, 127.0.0.1', array('127.0.0.1')), + + // invalid forwarded IP is ignored + array(array('88.88.88.88'), '127.0.0.1', 'unknown,88.88.88.88', array('127.0.0.1')), + array(array('88.88.88.88'), '127.0.0.1', '}__test|O:21:"JDatabaseDriverMysqli":3:{s:2,88.88.88.88', array('127.0.0.1')), + ); + } + + /** + * @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException + * @dataProvider getClientIpsWithConflictingHeadersProvider + */ + public function testGetClientIpsWithConflictingHeaders($httpForwarded, $httpXForwardedFor) + { + $request = new Request(); + + $server = array( + 'REMOTE_ADDR' => '88.88.88.88', + 'HTTP_FORWARDED' => $httpForwarded, + 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor, + ); + + Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_ALL | Request::HEADER_FORWARDED); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $request->getClientIps(); + } + + public function getClientIpsWithConflictingHeadersProvider() + { + // $httpForwarded $httpXForwardedFor + return array( + array('for=87.65.43.21', '192.0.2.60'), + array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60'), + array('for=192.0.2.60', '192.0.2.60,87.65.43.21'), + array('for="::face", for=192.0.2.60', '192.0.2.60,192.0.2.43'), + array('for=87.65.43.21, for=192.0.2.60', '192.0.2.60,87.65.43.21'), + ); + } + + /** + * @dataProvider getClientIpsWithAgreeingHeadersProvider + */ + public function testGetClientIpsWithAgreeingHeaders($httpForwarded, $httpXForwardedFor, $expectedIps) + { + $request = new Request(); + + $server = array( + 'REMOTE_ADDR' => '88.88.88.88', + 'HTTP_FORWARDED' => $httpForwarded, + 'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor, + ); + + Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_ALL); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $clientIps = $request->getClientIps(); + + $this->assertSame($expectedIps, $clientIps); + } + + public function getClientIpsWithAgreeingHeadersProvider() + { + // $httpForwarded $httpXForwardedFor + return array( + array('for="192.0.2.60"', '192.0.2.60', array('192.0.2.60')), + array('for=192.0.2.60, for=87.65.43.21', '192.0.2.60,87.65.43.21', array('87.65.43.21', '192.0.2.60')), + array('for="[::face]", for=192.0.2.60', '::face,192.0.2.60', array('192.0.2.60', '::face')), + array('for="192.0.2.60:80"', '192.0.2.60', array('192.0.2.60')), + array('for=192.0.2.60;proto=http;by=203.0.113.43', '192.0.2.60', array('192.0.2.60')), + array('for="[2001:db8:cafe::17]:4711"', '2001:db8:cafe::17', array('2001:db8:cafe::17')), + ); + } + + public function testGetContentWorksTwiceInDefaultMode() + { + $req = new Request(); + $this->assertEquals('', $req->getContent()); + $this->assertEquals('', $req->getContent()); + } + + public function testGetContentReturnsResource() + { + $req = new Request(); + $retval = $req->getContent(true); + $this->assertInternalType('resource', $retval); + $this->assertEquals('', fread($retval, 1)); + $this->assertTrue(feof($retval)); + } + + public function testGetContentReturnsResourceWhenContentSetInConstructor() + { + $req = new Request(array(), array(), array(), array(), array(), array(), 'MyContent'); + $resource = $req->getContent(true); + + $this->assertInternalType('resource', $resource); + $this->assertEquals('MyContent', stream_get_contents($resource)); + } + + public function testContentAsResource() + { + $resource = fopen('php://memory', 'r+'); + fwrite($resource, 'My other content'); + rewind($resource); + + $req = new Request(array(), array(), array(), array(), array(), array(), $resource); + $this->assertEquals('My other content', stream_get_contents($req->getContent(true))); + $this->assertEquals('My other content', $req->getContent()); + } + + /** + * @expectedException \LogicException + * @dataProvider getContentCantBeCalledTwiceWithResourcesProvider + */ + public function testGetContentCantBeCalledTwiceWithResources($first, $second) + { + if (\PHP_VERSION_ID >= 50600) { + $this->markTestSkipped('PHP >= 5.6 allows to open php://input several times.'); + } + + $req = new Request(); + $req->getContent($first); + $req->getContent($second); + } + + public function getContentCantBeCalledTwiceWithResourcesProvider() + { + return array( + 'Resource then fetch' => array(true, false), + 'Resource then resource' => array(true, true), + ); + } + + /** + * @dataProvider getContentCanBeCalledTwiceWithResourcesProvider + * @requires PHP 5.6 + */ + public function testGetContentCanBeCalledTwiceWithResources($first, $second) + { + $req = new Request(); + $a = $req->getContent($first); + $b = $req->getContent($second); + + if ($first) { + $a = stream_get_contents($a); + } + + if ($second) { + $b = stream_get_contents($b); + } + + $this->assertSame($a, $b); + } + + public function getContentCanBeCalledTwiceWithResourcesProvider() + { + return array( + 'Fetch then fetch' => array(false, false), + 'Fetch then resource' => array(false, true), + 'Resource then fetch' => array(true, false), + 'Resource then resource' => array(true, true), + ); + } + + public function provideOverloadedMethods() + { + return array( + array('PUT'), + array('DELETE'), + array('PATCH'), + array('put'), + array('delete'), + array('patch'), + ); + } + + /** + * @dataProvider provideOverloadedMethods + */ + public function testCreateFromGlobals($method) + { + $normalizedMethod = strtoupper($method); + + $_GET['foo1'] = 'bar1'; + $_POST['foo2'] = 'bar2'; + $_COOKIE['foo3'] = 'bar3'; + $_FILES['foo4'] = array('bar4'); + $_SERVER['foo5'] = 'bar5'; + + $request = Request::createFromGlobals(); + $this->assertEquals('bar1', $request->query->get('foo1'), '::fromGlobals() uses values from $_GET'); + $this->assertEquals('bar2', $request->request->get('foo2'), '::fromGlobals() uses values from $_POST'); + $this->assertEquals('bar3', $request->cookies->get('foo3'), '::fromGlobals() uses values from $_COOKIE'); + $this->assertEquals(array('bar4'), $request->files->get('foo4'), '::fromGlobals() uses values from $_FILES'); + $this->assertEquals('bar5', $request->server->get('foo5'), '::fromGlobals() uses values from $_SERVER'); + + unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']); + + $_SERVER['REQUEST_METHOD'] = $method; + $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + $request = RequestContentProxy::createFromGlobals(); + $this->assertEquals($normalizedMethod, $request->getMethod()); + $this->assertEquals('mycontent', $request->request->get('content')); + + unset($_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE']); + + Request::createFromGlobals(); + Request::enableHttpMethodParameterOverride(); + $_POST['_method'] = $method; + $_POST['foo6'] = 'bar6'; + $_SERVER['REQUEST_METHOD'] = 'PoSt'; + $request = Request::createFromGlobals(); + $this->assertEquals($normalizedMethod, $request->getMethod()); + $this->assertEquals('POST', $request->getRealMethod()); + $this->assertEquals('bar6', $request->request->get('foo6')); + + unset($_POST['_method'], $_POST['foo6'], $_SERVER['REQUEST_METHOD']); + $this->disableHttpMethodParameterOverride(); + } + + public function testOverrideGlobals() + { + $request = new Request(); + $request->initialize(array('foo' => 'bar')); + + // as the Request::overrideGlobals really work, it erase $_SERVER, so we must backup it + $server = $_SERVER; + + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_GET); + + $request->initialize(array(), array('foo' => 'bar')); + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_POST); + + $this->assertArrayNotHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + $request->headers->set('X_FORWARDED_PROTO', 'https'); + + Request::setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_ALL); + $this->assertFalse($request->isSecure()); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertTrue($request->isSecure()); + + $request->overrideGlobals(); + + $this->assertArrayHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + $request->headers->set('CONTENT_TYPE', 'multipart/form-data'); + $request->headers->set('CONTENT_LENGTH', 12345); + + $request->overrideGlobals(); + + $this->assertArrayHasKey('CONTENT_TYPE', $_SERVER); + $this->assertArrayHasKey('CONTENT_LENGTH', $_SERVER); + + $request->initialize(array('foo' => 'bar', 'baz' => 'foo')); + $request->query->remove('baz'); + + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_GET); + $this->assertEquals('foo=bar', $_SERVER['QUERY_STRING']); + $this->assertEquals('foo=bar', $request->server->get('QUERY_STRING')); + + // restore initial $_SERVER array + $_SERVER = $server; + } + + public function testGetScriptName() + { + $request = new Request(); + $this->assertEquals('', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + + $server = array(); + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/frontend.php', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + } + + public function testGetBasePath() + { + $request = new Request(); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['PHP_SELF'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + } + + public function testGetPathInfo() + { + $request = new Request(); + $this->assertEquals('/', $request->getPathInfo()); + + $server = array(); + $server['REQUEST_URI'] = '/path/info'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/path/info', $request->getPathInfo()); + + $server = array(); + $server['REQUEST_URI'] = '/path%20test/info'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/path%20test/info', $request->getPathInfo()); + } + + public function testGetParameterPrecedence() + { + $request = new Request(); + $request->attributes->set('foo', 'attr'); + $request->query->set('foo', 'query'); + $request->request->set('foo', 'body'); + + $this->assertSame('attr', $request->get('foo')); + + $request->attributes->remove('foo'); + $this->assertSame('query', $request->get('foo')); + + $request->query->remove('foo'); + $this->assertSame('body', $request->get('foo')); + + $request->request->remove('foo'); + $this->assertNull($request->get('foo')); + } + + public function testGetPreferredLanguage() + { + $request = new Request(); + $this->assertNull($request->getPreferredLanguage()); + $this->assertNull($request->getPreferredLanguage(array())); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr'))); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr', 'en'))); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'fr'))); + $this->assertEquals('fr-ch', $request->getPreferredLanguage(array('fr-ch', 'fr-fr'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'en-us'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, fr-fr; q=0.6, fr; q=0.5'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + } + + public function testIsXmlHttpRequest() + { + $request = new Request(); + $this->assertFalse($request->isXmlHttpRequest()); + + $request->headers->set('X-Requested-With', 'XMLHttpRequest'); + $this->assertTrue($request->isXmlHttpRequest()); + + $request->headers->remove('X-Requested-With'); + $this->assertFalse($request->isXmlHttpRequest()); + } + + /** + * @requires extension intl + */ + public function testIntlLocale() + { + $request = new Request(); + + $request->setDefaultLocale('fr'); + $this->assertEquals('fr', $request->getLocale()); + $this->assertEquals('fr', \Locale::getDefault()); + + $request->setLocale('en'); + $this->assertEquals('en', $request->getLocale()); + $this->assertEquals('en', \Locale::getDefault()); + + $request->setDefaultLocale('de'); + $this->assertEquals('en', $request->getLocale()); + $this->assertEquals('en', \Locale::getDefault()); + } + + public function testGetCharsets() + { + $request = new Request(); + $this->assertEquals(array(), $request->getCharsets()); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array(), $request->getCharsets()); // testing caching + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array('ISO-8859-1', 'US-ASCII', 'UTF-8', 'ISO-10646-UCS-2'), $request->getCharsets()); + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'); + $this->assertEquals(array('ISO-8859-1', 'utf-8', '*'), $request->getCharsets()); + } + + public function testGetEncodings() + { + $request = new Request(); + $this->assertEquals(array(), $request->getEncodings()); + $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch'); + $this->assertEquals(array(), $request->getEncodings()); // testing caching + + $request = new Request(); + $request->headers->set('Accept-Encoding', 'gzip,deflate,sdch'); + $this->assertEquals(array('gzip', 'deflate', 'sdch'), $request->getEncodings()); + + $request = new Request(); + $request->headers->set('Accept-Encoding', 'gzip;q=0.4,deflate;q=0.9,compress;q=0.7'); + $this->assertEquals(array('deflate', 'compress', 'gzip'), $request->getEncodings()); + } + + public function testGetAcceptableContentTypes() + { + $request = new Request(); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); // testing caching + + $request = new Request(); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array('application/vnd.wap.wmlscriptc', 'text/vnd.wap.wml', 'application/vnd.wap.xhtml+xml', 'application/xhtml+xml', 'text/html', 'multipart/mixed', '*/*'), $request->getAcceptableContentTypes()); + } + + public function testGetLanguages() + { + $request = new Request(); + $this->assertEquals(array(), $request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.6, en; q=0.8'); + $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test out of order qvalues + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en, en-us'); + $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test equal weighting without qvalues + + $request = new Request(); + $request->headers->set('Accept-language', 'zh; q=0.6, en, en-us; q=0.6'); + $this->assertEquals(array('en', 'zh', 'en_US'), $request->getLanguages()); // Test equal weighting with qvalues + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, i-cherokee; q=0.6'); + $this->assertEquals(array('zh', 'cherokee'), $request->getLanguages()); + } + + public function testGetRequestFormat() + { + $request = new Request(); + $this->assertEquals('html', $request->getRequestFormat()); + + // Ensure that setting different default values over time is possible, + // aka. setRequestFormat determines the state. + $this->assertEquals('json', $request->getRequestFormat('json')); + $this->assertEquals('html', $request->getRequestFormat('html')); + + $request = new Request(); + $this->assertNull($request->getRequestFormat(null)); + + $request = new Request(); + $this->assertNull($request->setRequestFormat('foo')); + $this->assertEquals('foo', $request->getRequestFormat(null)); + + $request = new Request(array('_format' => 'foo')); + $this->assertEquals('html', $request->getRequestFormat()); + } + + public function testHasSession() + { + $request = new Request(); + + $this->assertFalse($request->hasSession()); + $request->setSession(new Session(new MockArraySessionStorage())); + $this->assertTrue($request->hasSession()); + } + + public function testGetSession() + { + $request = new Request(); + + $request->setSession(new Session(new MockArraySessionStorage())); + $this->assertTrue($request->hasSession()); + + $session = $request->getSession(); + $this->assertObjectHasAttribute('storage', $session); + $this->assertObjectHasAttribute('flashName', $session); + $this->assertObjectHasAttribute('attributeName', $session); + } + + public function testHasPreviousSession() + { + $request = new Request(); + + $this->assertFalse($request->hasPreviousSession()); + $request->cookies->set('MOCKSESSID', 'foo'); + $this->assertFalse($request->hasPreviousSession()); + $request->setSession(new Session(new MockArraySessionStorage())); + $this->assertTrue($request->hasPreviousSession()); + } + + public function testToString() + { + $request = new Request(); + + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + + $this->assertContains('Accept-Language: zh, en-us; q=0.8, en; q=0.6', $request->__toString()); + } + + public function testIsMethod() + { + $request = new Request(); + $request->setMethod('POST'); + $this->assertTrue($request->isMethod('POST')); + $this->assertTrue($request->isMethod('post')); + $this->assertFalse($request->isMethod('GET')); + $this->assertFalse($request->isMethod('get')); + + $request->setMethod('GET'); + $this->assertTrue($request->isMethod('GET')); + $this->assertTrue($request->isMethod('get')); + $this->assertFalse($request->isMethod('POST')); + $this->assertFalse($request->isMethod('post')); + } + + /** + * @dataProvider getBaseUrlData + */ + public function testGetBaseUrl($uri, $server, $expectedBaseUrl, $expectedPathInfo) + { + $request = Request::create($uri, 'GET', array(), array(), array(), $server); + + $this->assertSame($expectedBaseUrl, $request->getBaseUrl(), 'baseUrl'); + $this->assertSame($expectedPathInfo, $request->getPathInfo(), 'pathInfo'); + } + + public function getBaseUrlData() + { + return array( + array( + '/fruit/strawberry/1234index.php/blah', + array( + 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/fruit/index.php', + 'SCRIPT_NAME' => '/fruit/index.php', + 'PHP_SELF' => '/fruit/index.php', + ), + '/fruit', + '/strawberry/1234index.php/blah', + ), + array( + '/fruit/strawberry/1234index.php/blah', + array( + 'SCRIPT_FILENAME' => 'E:/Sites/cc-new/public_html/index.php', + 'SCRIPT_NAME' => '/index.php', + 'PHP_SELF' => '/index.php', + ), + '', + '/fruit/strawberry/1234index.php/blah', + ), + array( + '/foo%20bar/', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar', + '/', + ), + array( + '/foo%20bar/home', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar', + '/home', + ), + array( + '/foo%20bar/app.php/home', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar/app.php', + '/home', + ), + array( + '/foo%20bar/app.php/home%3Dbaz', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar/app.php', + '/home%3Dbaz', + ), + array( + '/foo/bar+baz', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo/app.php', + 'SCRIPT_NAME' => '/foo/app.php', + 'PHP_SELF' => '/foo/app.php', + ), + '/foo', + '/bar+baz', + ), + ); + } + + /** + * @dataProvider urlencodedStringPrefixData + */ + public function testUrlencodedStringPrefix($string, $prefix, $expect) + { + $request = new Request(); + + $me = new \ReflectionMethod($request, 'getUrlencodedPrefix'); + $me->setAccessible(true); + + $this->assertSame($expect, $me->invoke($request, $string, $prefix)); + } + + public function urlencodedStringPrefixData() + { + return array( + array('foo', 'foo', 'foo'), + array('fo%6f', 'foo', 'fo%6f'), + array('foo/bar', 'foo', 'foo'), + array('fo%6f/bar', 'foo', 'fo%6f'), + array('f%6f%6f/bar', 'foo', 'f%6f%6f'), + array('%66%6F%6F/bar', 'foo', '%66%6F%6F'), + array('fo+o/bar', 'fo+o', 'fo+o'), + array('fo%2Bo/bar', 'fo+o', 'fo%2Bo'), + ); + } + + private function disableHttpMethodParameterOverride() + { + $class = new \ReflectionClass('Symfony\\Component\\HttpFoundation\\Request'); + $property = $class->getProperty('httpMethodParameterOverride'); + $property->setAccessible(true); + $property->setValue(false); + } + + private function getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = new Request(); + + $server = array('REMOTE_ADDR' => $remoteAddr); + if (null !== $httpForwardedFor) { + $server['HTTP_X_FORWARDED_FOR'] = $httpForwardedFor; + } + + if ($trustedProxies) { + Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL); + } + + $request->initialize(array(), array(), array(), array(), array(), $server); + + return $request; + } + + private function getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies) + { + $request = new Request(); + + $server = array('REMOTE_ADDR' => $remoteAddr); + + if (null !== $httpForwarded) { + $server['HTTP_FORWARDED'] = $httpForwarded; + } + + if ($trustedProxies) { + Request::setTrustedProxies($trustedProxies, Request::HEADER_FORWARDED); + } + + $request->initialize(array(), array(), array(), array(), array(), $server); + + return $request; + } + + public function testTrustedProxiesXForwardedFor() + { + $request = Request::create('http://example.com/'); + $request->server->set('REMOTE_ADDR', '3.3.3.3'); + $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2'); + $request->headers->set('X_FORWARDED_HOST', 'foo.example.com:1234, real.example.com:8080'); + $request->headers->set('X_FORWARDED_PROTO', 'https'); + $request->headers->set('X_FORWARDED_PORT', 443); + + // no trusted proxies + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // disabling proxy trusting + Request::setTrustedProxies(array(), Request::HEADER_X_FORWARDED_ALL); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // request is forwarded by a non-trusted proxy + Request::setTrustedProxies(array('2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // trusted proxy via setTrustedProxies() + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + $this->assertEquals('1.1.1.1', $request->getClientIp()); + $this->assertEquals('foo.example.com', $request->getHost()); + $this->assertEquals(443, $request->getPort()); + $this->assertTrue($request->isSecure()); + + // trusted proxy via setTrustedProxies() + Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // check various X_FORWARDED_PROTO header values + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + $request->headers->set('X_FORWARDED_PROTO', 'ssl'); + $this->assertTrue($request->isSecure()); + + $request->headers->set('X_FORWARDED_PROTO', 'https, http'); + $this->assertTrue($request->isSecure()); + } + + /** + * @group legacy + * @expectedDeprecation The "Symfony\Component\HttpFoundation\Request::setTrustedHeaderName()" method is deprecated since version 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead. + */ + public function testLegacyTrustedProxies() + { + $request = Request::create('http://example.com/'); + $request->server->set('REMOTE_ADDR', '3.3.3.3'); + $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2'); + $request->headers->set('X_FORWARDED_HOST', 'foo.example.com, real.example.com:8080'); + $request->headers->set('X_FORWARDED_PROTO', 'https'); + $request->headers->set('X_FORWARDED_PORT', 443); + $request->headers->set('X_MY_FOR', '3.3.3.3, 4.4.4.4'); + $request->headers->set('X_MY_HOST', 'my.example.com'); + $request->headers->set('X_MY_PROTO', 'http'); + $request->headers->set('X_MY_PORT', 81); + + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_X_FORWARDED_ALL); + + // custom header names + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_MY_FOR'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_MY_HOST'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_MY_PORT'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_MY_PROTO'); + $this->assertEquals('4.4.4.4', $request->getClientIp()); + $this->assertEquals('my.example.com', $request->getHost()); + $this->assertEquals(81, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // disabling via empty header names + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, null); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, null); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, null); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, null); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + //reset + Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO'); + } + + public function testTrustedProxiesForwarded() + { + $request = Request::create('http://example.com/'); + $request->server->set('REMOTE_ADDR', '3.3.3.3'); + $request->headers->set('FORWARDED', 'for=1.1.1.1, host=foo.example.com:8080, proto=https, for=2.2.2.2, host=real.example.com:8080'); + + // no trusted proxies + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // disabling proxy trusting + Request::setTrustedProxies(array(), Request::HEADER_FORWARDED); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // request is forwarded by a non-trusted proxy + Request::setTrustedProxies(array('2.2.2.2'), Request::HEADER_FORWARDED); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // trusted proxy via setTrustedProxies() + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_FORWARDED); + $this->assertEquals('1.1.1.1', $request->getClientIp()); + $this->assertEquals('foo.example.com', $request->getHost()); + $this->assertEquals(8080, $request->getPort()); + $this->assertTrue($request->isSecure()); + + // trusted proxy via setTrustedProxies() + Request::setTrustedProxies(array('3.3.3.4', '2.2.2.2'), Request::HEADER_FORWARDED); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // check various X_FORWARDED_PROTO header values + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'), Request::HEADER_FORWARDED); + $request->headers->set('FORWARDED', 'proto=ssl'); + $this->assertTrue($request->isSecure()); + + $request->headers->set('FORWARDED', 'proto=https, proto=http'); + $this->assertTrue($request->isSecure()); + } + + /** + * @group legacy + * @expectedException \InvalidArgumentException + */ + public function testSetTrustedProxiesInvalidHeaderName() + { + Request::create('http://example.com/'); + Request::setTrustedHeaderName('bogus name', 'X_MY_FOR'); + } + + /** + * @group legacy + * @expectedException \InvalidArgumentException + */ + public function testGetTrustedProxiesInvalidHeaderName() + { + Request::create('http://example.com/'); + Request::getTrustedHeaderName('bogus name'); + } + + /** + * @dataProvider iisRequestUriProvider + */ + public function testIISRequestUri($headers, $server, $expectedRequestUri) + { + $request = new Request(); + $request->headers->replace($headers); + $request->server->replace($server); + + $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct'); + + $subRequestUri = '/bar/foo'; + $subRequest = Request::create($subRequestUri, 'get', array(), array(), array(), $request->server->all()); + $this->assertEquals($subRequestUri, $subRequest->getRequestUri(), '->getRequestUri() is correct in sub request'); + } + + public function iisRequestUriProvider() + { + return array( + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array(), + '/foo/bar', + ), + array( + array( + 'X_REWRITE_URL' => '/foo/bar', + ), + array(), + '/foo/bar', + ), + array( + array(), + array( + 'IIS_WasUrlRewritten' => '1', + 'UNENCODED_URL' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array( + 'HTTP_X_ORIGINAL_URL' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array( + 'IIS_WasUrlRewritten' => '1', + 'UNENCODED_URL' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array( + 'HTTP_X_ORIGINAL_URL' => '/foo/bar', + 'IIS_WasUrlRewritten' => '1', + 'UNENCODED_URL' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array(), + array( + 'ORIG_PATH_INFO' => '/foo/bar', + ), + '/foo/bar', + ), + array( + array(), + array( + 'ORIG_PATH_INFO' => '/foo/bar', + 'QUERY_STRING' => 'foo=bar', + ), + '/foo/bar?foo=bar', + ), + ); + } + + public function testTrustedHosts() + { + // create a request + $request = Request::create('/'); + + // no trusted host set -> no host check + $request->headers->set('host', 'evil.com'); + $this->assertEquals('evil.com', $request->getHost()); + + // add a trusted domain and all its subdomains + Request::setTrustedHosts(array('^([a-z]{9}\.)?trusted\.com$')); + + // untrusted host + $request->headers->set('host', 'evil.com'); + try { + $request->getHost(); + $this->fail('Request::getHost() should throw an exception when host is not trusted.'); + } catch (SuspiciousOperationException $e) { + $this->assertEquals('Untrusted Host "evil.com".', $e->getMessage()); + } + + // trusted hosts + $request->headers->set('host', 'trusted.com'); + $this->assertEquals('trusted.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + + $request->server->set('HTTPS', true); + $request->headers->set('host', 'trusted.com'); + $this->assertEquals('trusted.com', $request->getHost()); + $this->assertEquals(443, $request->getPort()); + $request->server->set('HTTPS', false); + + $request->headers->set('host', 'trusted.com:8000'); + $this->assertEquals('trusted.com', $request->getHost()); + $this->assertEquals(8000, $request->getPort()); + + $request->headers->set('host', 'subdomain.trusted.com'); + $this->assertEquals('subdomain.trusted.com', $request->getHost()); + + // reset request for following tests + Request::setTrustedHosts(array()); + } + + public function testFactory() + { + Request::setFactory(function (array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) { + return new NewRequest(); + }); + + $this->assertEquals('foo', Request::create('/')->getFoo()); + + Request::setFactory(null); + } + + /** + * @dataProvider getLongHostNames + */ + public function testVeryLongHosts($host) + { + $start = microtime(true); + + $request = Request::create('/'); + $request->headers->set('host', $host); + $this->assertEquals($host, $request->getHost()); + $this->assertLessThan(5, microtime(true) - $start); + } + + /** + * @dataProvider getHostValidities + */ + public function testHostValidity($host, $isValid, $expectedHost = null, $expectedPort = null) + { + $request = Request::create('/'); + $request->headers->set('host', $host); + + if ($isValid) { + $this->assertSame($expectedHost ?: $host, $request->getHost()); + if ($expectedPort) { + $this->assertSame($expectedPort, $request->getPort()); + } + } else { + if (method_exists($this, 'expectException')) { + $this->expectException(SuspiciousOperationException::class); + $this->expectExceptionMessage('Invalid Host'); + } else { + $this->setExpectedException(SuspiciousOperationException::class, 'Invalid Host'); + } + + $request->getHost(); + } + } + + public function getHostValidities() + { + return array( + array('.a', false), + array('a..', false), + array('a.', true), + array("\xE9", false), + array('[::1]', true), + array('[::1]:80', true, '[::1]', 80), + array(str_repeat('.', 101), false), + ); + } + + public function getLongHostNames() + { + return array( + array('a'.str_repeat('.a', 40000)), + array(str_repeat(':', 101)), + ); + } + + /** + * @dataProvider methodIdempotentProvider + */ + public function testMethodIdempotent($method, $idempotent) + { + $request = new Request(); + $request->setMethod($method); + $this->assertEquals($idempotent, $request->isMethodIdempotent()); + } + + public function methodIdempotentProvider() + { + return array( + array('HEAD', true), + array('GET', true), + array('POST', false), + array('PUT', true), + array('PATCH', false), + array('DELETE', true), + array('PURGE', true), + array('OPTIONS', true), + array('TRACE', true), + array('CONNECT', false), + ); + } + + /** + * @dataProvider methodSafeProvider + */ + public function testMethodSafe($method, $safe) + { + $request = new Request(); + $request->setMethod($method); + $this->assertEquals($safe, $request->isMethodSafe(false)); + } + + public function methodSafeProvider() + { + return array( + array('HEAD', true), + array('GET', true), + array('POST', false), + array('PUT', false), + array('PATCH', false), + array('DELETE', false), + array('PURGE', false), + array('OPTIONS', true), + array('TRACE', true), + array('CONNECT', false), + ); + } + + /** + * @group legacy + * @expectedDeprecation Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since version 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead. + */ + public function testMethodSafeChecksCacheable() + { + $request = new Request(); + $request->setMethod('OPTIONS'); + $this->assertFalse($request->isMethodSafe()); + } + + /** + * @dataProvider methodCacheableProvider + */ + public function testMethodCacheable($method, $chacheable) + { + $request = new Request(); + $request->setMethod($method); + $this->assertEquals($chacheable, $request->isMethodCacheable()); + } + + public function methodCacheableProvider() + { + return array( + array('HEAD', true), + array('GET', true), + array('POST', false), + array('PUT', false), + array('PATCH', false), + array('DELETE', false), + array('PURGE', false), + array('OPTIONS', false), + array('TRACE', false), + array('CONNECT', false), + ); + } + + /** + * @group legacy + */ + public function testGetTrustedHeaderName() + { + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_X_FORWARDED_ALL); + + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + $this->assertSame('X_FORWARDED_FOR', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)); + $this->assertSame('X_FORWARDED_HOST', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST)); + $this->assertSame('X_FORWARDED_PORT', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT)); + $this->assertSame('X_FORWARDED_PROTO', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO)); + + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED); + + $this->assertSame('FORWARDED', Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO)); + + Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'A'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'B'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'C'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'D'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'E'); + + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED); + + $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT)); + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO)); + + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_X_FORWARDED_ALL); + + $this->assertNull(Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + $this->assertSame('B', Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)); + $this->assertSame('C', Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST)); + $this->assertSame('D', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT)); + $this->assertSame('E', Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO)); + + Request::setTrustedProxies(array('8.8.8.8'), Request::HEADER_FORWARDED); + + $this->assertSame('A', Request::getTrustedHeaderName(Request::HEADER_FORWARDED)); + + //reset + Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'FORWARDED'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO'); + } +} + +class RequestContentProxy extends Request +{ + public function getContent($asResource = false) + { + return http_build_query(array('_method' => 'PUT', 'content' => 'mycontent')); + } +} + +class NewRequest extends Request +{ + public function getFoo() + { + return 'foo'; + } +} diff --git a/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php b/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php new file mode 100644 index 00000000..724328ae --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php @@ -0,0 +1,339 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\Cookie; + +/** + * @group time-sensitive + */ +class ResponseHeaderBagTest extends TestCase +{ + /** + * @dataProvider provideAllPreserveCase + */ + public function testAllPreserveCase($headers, $expected) + { + $bag = new ResponseHeaderBag($headers); + + $this->assertEquals($expected, $bag->allPreserveCase(), '->allPreserveCase() gets all input keys in original case'); + } + + public function provideAllPreserveCase() + { + return array( + array( + array('fOo' => 'BAR'), + array('fOo' => array('BAR'), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('ETag' => 'xyzzy'), + array('ETag' => array('xyzzy'), 'Cache-Control' => array('private, must-revalidate')), + ), + array( + array('Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ=='), + array('Content-MD5' => array('Q2hlY2sgSW50ZWdyaXR5IQ=='), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('P3P' => 'CP="CAO PSA OUR"'), + array('P3P' => array('CP="CAO PSA OUR"'), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('WWW-Authenticate' => 'Basic realm="WallyWorld"'), + array('WWW-Authenticate' => array('Basic realm="WallyWorld"'), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('X-UA-Compatible' => 'IE=edge,chrome=1'), + array('X-UA-Compatible' => array('IE=edge,chrome=1'), 'Cache-Control' => array('no-cache, private')), + ), + array( + array('X-XSS-Protection' => '1; mode=block'), + array('X-XSS-Protection' => array('1; mode=block'), 'Cache-Control' => array('no-cache, private')), + ), + ); + } + + public function testCacheControlHeader() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag = new ResponseHeaderBag(array('Cache-Control' => 'public')); + $this->assertEquals('public', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + + $bag = new ResponseHeaderBag(array('ETag' => 'abcde')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('private')); + $this->assertTrue($bag->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($bag->hasCacheControlDirective('max-age')); + + $bag = new ResponseHeaderBag(array('Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array( + 'Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT', + 'Cache-Control' => 'max-age=3600', + )); + $this->assertEquals('max-age=3600, private', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('Last-Modified' => 'abcde')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('Etag' => 'abcde', 'Last-Modified' => 'abcde')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 'max-age=100')); + $this->assertEquals('max-age=100, private', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 's-maxage=100')); + $this->assertEquals('s-maxage=100', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 'private, max-age=100')); + $this->assertEquals('max-age=100, private', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 'public, max-age=100')); + $this->assertEquals('max-age=100, public', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(); + $bag->set('Last-Modified', 'abcde'); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + } + + public function testCacheControlClone() + { + $headers = array('foo' => 'bar'); + $bag1 = new ResponseHeaderBag($headers); + $bag2 = new ResponseHeaderBag($bag1->allPreserveCase()); + $this->assertEquals($bag1->allPreserveCase(), $bag2->allPreserveCase()); + } + + public function testToStringIncludesCookieHeaders() + { + $bag = new ResponseHeaderBag(array()); + $bag->setCookie(new Cookie('foo', 'bar')); + + $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag); + + $bag->clearCookie('foo'); + + $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; httponly', $bag); + } + + public function testClearCookieSecureNotHttpOnly() + { + $bag = new ResponseHeaderBag(array()); + + $bag->clearCookie('foo', '/', null, true, false); + + $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; secure', $bag); + } + + public function testReplace() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag->replace(array('Cache-Control' => 'public')); + $this->assertEquals('public', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + } + + public function testReplaceWithRemove() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag->remove('Cache-Control'); + $bag->replace(array()); + $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + } + + public function testCookiesWithSameNames() + { + $bag = new ResponseHeaderBag(); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar')); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'foo.bar')); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'bar.foo')); + $bag->setCookie(new Cookie('foo', 'bar')); + + $this->assertCount(4, $bag->getCookies()); + $this->assertEquals('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag->get('set-cookie')); + $this->assertEquals(array( + 'foo=bar; path=/path/foo; domain=foo.bar; httponly', + 'foo=bar; path=/path/bar; domain=foo.bar; httponly', + 'foo=bar; path=/path/bar; domain=bar.foo; httponly', + 'foo=bar; path=/; httponly', + ), $bag->get('set-cookie', null, false)); + + $this->assertSetCookieHeader('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag); + $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=foo.bar; httponly', $bag); + $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=bar.foo; httponly', $bag); + $this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + + $this->assertTrue(isset($cookies['foo.bar']['/path/foo']['foo'])); + $this->assertTrue(isset($cookies['foo.bar']['/path/bar']['foo'])); + $this->assertTrue(isset($cookies['bar.foo']['/path/bar']['foo'])); + $this->assertTrue(isset($cookies['']['/']['foo'])); + } + + public function testRemoveCookie() + { + $bag = new ResponseHeaderBag(); + $this->assertFalse($bag->has('set-cookie')); + + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar')); + $bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar')); + $this->assertTrue($bag->has('set-cookie')); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertTrue(isset($cookies['foo.bar']['/path/foo'])); + + $bag->removeCookie('foo', '/path/foo', 'foo.bar'); + $this->assertTrue($bag->has('set-cookie')); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['foo.bar']['/path/foo'])); + + $bag->removeCookie('bar', '/path/bar', 'foo.bar'); + $this->assertFalse($bag->has('set-cookie')); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['foo.bar'])); + } + + public function testRemoveCookieWithNullRemove() + { + $bag = new ResponseHeaderBag(); + $bag->setCookie(new Cookie('foo', 'bar', 0)); + $bag->setCookie(new Cookie('bar', 'foo', 0)); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertTrue(isset($cookies['']['/'])); + + $bag->removeCookie('foo', null); + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['']['/']['foo'])); + + $bag->removeCookie('bar', null); + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['']['/']['bar'])); + } + + public function testSetCookieHeader() + { + $bag = new ResponseHeaderBag(); + $bag->set('set-cookie', 'foo=bar'); + $this->assertEquals(array(new Cookie('foo', 'bar', 0, '/', null, false, true, true)), $bag->getCookies()); + + $bag->set('set-cookie', 'foo2=bar2', false); + $this->assertEquals(array( + new Cookie('foo', 'bar', 0, '/', null, false, true, true), + new Cookie('foo2', 'bar2', 0, '/', null, false, true, true), + ), $bag->getCookies()); + + $bag->remove('set-cookie'); + $this->assertEquals(array(), $bag->getCookies()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetCookiesWithInvalidArgument() + { + $bag = new ResponseHeaderBag(); + + $bag->getCookies('invalid_argument'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testMakeDispositionInvalidDisposition() + { + $headers = new ResponseHeaderBag(); + + $headers->makeDisposition('invalid', 'foo.html'); + } + + /** + * @dataProvider provideMakeDisposition + */ + public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected) + { + $headers = new ResponseHeaderBag(); + + $this->assertEquals($expected, $headers->makeDisposition($disposition, $filename, $filenameFallback)); + } + + public function testToStringDoesntMessUpHeaders() + { + $headers = new ResponseHeaderBag(); + + $headers->set('Location', 'http://www.symfony.com'); + $headers->set('Content-type', 'text/html'); + + (string) $headers; + + $allHeaders = $headers->allPreserveCase(); + $this->assertEquals(array('http://www.symfony.com'), $allHeaders['Location']); + $this->assertEquals(array('text/html'), $allHeaders['Content-type']); + } + + public function provideMakeDisposition() + { + return array( + array('attachment', 'foo.html', 'foo.html', 'attachment; filename="foo.html"'), + array('attachment', 'foo.html', '', 'attachment; filename="foo.html"'), + array('attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'), + array('attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'), + array('attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'), + array('attachment', 'föö.html', 'foo.html', 'attachment; filename="foo.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html'), + ); + } + + /** + * @dataProvider provideMakeDispositionFail + * @expectedException \InvalidArgumentException + */ + public function testMakeDispositionFail($disposition, $filename) + { + $headers = new ResponseHeaderBag(); + + $headers->makeDisposition($disposition, $filename); + } + + public function provideMakeDispositionFail() + { + return array( + array('attachment', 'foo%20bar.html'), + array('attachment', 'foo/bar.html'), + array('attachment', '/foo.html'), + array('attachment', 'foo\bar.html'), + array('attachment', '\foo.html'), + array('attachment', 'föö.html'), + ); + } + + private function assertSetCookieHeader($expected, ResponseHeaderBag $actual) + { + $this->assertRegExp('#^Set-Cookie:\s+'.preg_quote($expected, '#').'$#m', str_replace("\r\n", "\n", (string) $actual)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/ResponseTest.php b/vendor/symfony/http-foundation/Tests/ResponseTest.php new file mode 100644 index 00000000..62b8c652 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ResponseTest.php @@ -0,0 +1,981 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class ResponseTest extends ResponseTestCase +{ + public function testCreate() + { + $response = Response::create('foo', 301, array('Foo' => 'bar')); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertEquals(301, $response->getStatusCode()); + $this->assertEquals('bar', $response->headers->get('foo')); + } + + public function testToString() + { + $response = new Response(); + $response = explode("\r\n", $response); + $this->assertEquals('HTTP/1.0 200 OK', $response[0]); + $this->assertEquals('Cache-Control: no-cache, private', $response[1]); + } + + public function testClone() + { + $response = new Response(); + $responseClone = clone $response; + $this->assertEquals($response, $responseClone); + } + + public function testSendHeaders() + { + $response = new Response(); + $headers = $response->sendHeaders(); + $this->assertObjectHasAttribute('headers', $headers); + $this->assertObjectHasAttribute('content', $headers); + $this->assertObjectHasAttribute('version', $headers); + $this->assertObjectHasAttribute('statusCode', $headers); + $this->assertObjectHasAttribute('statusText', $headers); + $this->assertObjectHasAttribute('charset', $headers); + } + + public function testSend() + { + $response = new Response(); + $responseSend = $response->send(); + $this->assertObjectHasAttribute('headers', $responseSend); + $this->assertObjectHasAttribute('content', $responseSend); + $this->assertObjectHasAttribute('version', $responseSend); + $this->assertObjectHasAttribute('statusCode', $responseSend); + $this->assertObjectHasAttribute('statusText', $responseSend); + $this->assertObjectHasAttribute('charset', $responseSend); + } + + public function testGetCharset() + { + $response = new Response(); + $charsetOrigin = 'UTF-8'; + $response->setCharset($charsetOrigin); + $charset = $response->getCharset(); + $this->assertEquals($charsetOrigin, $charset); + } + + public function testIsCacheable() + { + $response = new Response(); + $this->assertFalse($response->isCacheable()); + } + + public function testIsCacheableWithErrorCode() + { + $response = new Response('', 500); + $this->assertFalse($response->isCacheable()); + } + + public function testIsCacheableWithNoStoreDirective() + { + $response = new Response(); + $response->headers->set('cache-control', 'private'); + $this->assertFalse($response->isCacheable()); + } + + public function testIsCacheableWithSetTtl() + { + $response = new Response(); + $response->setTtl(10); + $this->assertTrue($response->isCacheable()); + } + + public function testMustRevalidate() + { + $response = new Response(); + $this->assertFalse($response->mustRevalidate()); + } + + public function testMustRevalidateWithMustRevalidateCacheControlHeader() + { + $response = new Response(); + $response->headers->set('cache-control', 'must-revalidate'); + + $this->assertTrue($response->mustRevalidate()); + } + + public function testMustRevalidateWithProxyRevalidateCacheControlHeader() + { + $response = new Response(); + $response->headers->set('cache-control', 'proxy-revalidate'); + + $this->assertTrue($response->mustRevalidate()); + } + + public function testSetNotModified() + { + $response = new Response(); + $modified = $response->setNotModified(); + $this->assertObjectHasAttribute('headers', $modified); + $this->assertObjectHasAttribute('content', $modified); + $this->assertObjectHasAttribute('version', $modified); + $this->assertObjectHasAttribute('statusCode', $modified); + $this->assertObjectHasAttribute('statusText', $modified); + $this->assertObjectHasAttribute('charset', $modified); + $this->assertEquals(304, $modified->getStatusCode()); + } + + public function testIsSuccessful() + { + $response = new Response(); + $this->assertTrue($response->isSuccessful()); + } + + public function testIsNotModified() + { + $response = new Response(); + $modified = $response->isNotModified(new Request()); + $this->assertFalse($modified); + } + + public function testIsNotModifiedNotSafe() + { + $request = Request::create('/homepage', 'POST'); + + $response = new Response(); + $this->assertFalse($response->isNotModified($request)); + } + + public function testIsNotModifiedLastModified() + { + $before = 'Sun, 25 Aug 2013 18:32:31 GMT'; + $modified = 'Sun, 25 Aug 2013 18:33:31 GMT'; + $after = 'Sun, 25 Aug 2013 19:33:31 GMT'; + + $request = new Request(); + $request->headers->set('If-Modified-Since', $modified); + + $response = new Response(); + + $response->headers->set('Last-Modified', $modified); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('Last-Modified', $before); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('Last-Modified', $after); + $this->assertFalse($response->isNotModified($request)); + + $response->headers->set('Last-Modified', ''); + $this->assertFalse($response->isNotModified($request)); + } + + public function testIsNotModifiedEtag() + { + $etagOne = 'randomly_generated_etag'; + $etagTwo = 'randomly_generated_etag_2'; + + $request = new Request(); + $request->headers->set('if_none_match', sprintf('%s, %s, %s', $etagOne, $etagTwo, 'etagThree')); + + $response = new Response(); + + $response->headers->set('ETag', $etagOne); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('ETag', $etagTwo); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('ETag', ''); + $this->assertFalse($response->isNotModified($request)); + } + + public function testIsNotModifiedLastModifiedAndEtag() + { + $before = 'Sun, 25 Aug 2013 18:32:31 GMT'; + $modified = 'Sun, 25 Aug 2013 18:33:31 GMT'; + $after = 'Sun, 25 Aug 2013 19:33:31 GMT'; + $etag = 'randomly_generated_etag'; + + $request = new Request(); + $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree')); + $request->headers->set('If-Modified-Since', $modified); + + $response = new Response(); + + $response->headers->set('ETag', $etag); + $response->headers->set('Last-Modified', $after); + $this->assertFalse($response->isNotModified($request)); + + $response->headers->set('ETag', 'non-existent-etag'); + $response->headers->set('Last-Modified', $before); + $this->assertFalse($response->isNotModified($request)); + + $response->headers->set('ETag', $etag); + $response->headers->set('Last-Modified', $modified); + $this->assertTrue($response->isNotModified($request)); + } + + public function testIsNotModifiedIfModifiedSinceAndEtagWithoutLastModified() + { + $modified = 'Sun, 25 Aug 2013 18:33:31 GMT'; + $etag = 'randomly_generated_etag'; + + $request = new Request(); + $request->headers->set('if_none_match', sprintf('%s, %s', $etag, 'etagThree')); + $request->headers->set('If-Modified-Since', $modified); + + $response = new Response(); + + $response->headers->set('ETag', $etag); + $this->assertTrue($response->isNotModified($request)); + + $response->headers->set('ETag', 'non-existent-etag'); + $this->assertFalse($response->isNotModified($request)); + } + + public function testIsValidateable() + { + $response = new Response('', 200, array('Last-Modified' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822))); + $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if Last-Modified is present'); + + $response = new Response('', 200, array('ETag' => '"12345"')); + $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if ETag is present'); + + $response = new Response(); + $this->assertFalse($response->isValidateable(), '->isValidateable() returns false when no validator is present'); + } + + public function testGetDate() + { + $oneHourAgo = $this->createDateTimeOneHourAgo(); + $response = new Response('', 200, array('Date' => $oneHourAgo->format(DATE_RFC2822))); + $date = $response->getDate(); + $this->assertEquals($oneHourAgo->getTimestamp(), $date->getTimestamp(), '->getDate() returns the Date header if present'); + + $response = new Response(); + $date = $response->getDate(); + $this->assertEquals(time(), $date->getTimestamp(), '->getDate() returns the current Date if no Date header present'); + + $response = new Response('', 200, array('Date' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822))); + $now = $this->createDateTimeNow(); + $response->headers->set('Date', $now->format(DATE_RFC2822)); + $date = $response->getDate(); + $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the date when the header has been modified'); + + $response = new Response('', 200); + $now = $this->createDateTimeNow(); + $response->headers->remove('Date'); + $date = $response->getDate(); + $this->assertEquals($now->getTimestamp(), $date->getTimestamp(), '->getDate() returns the current Date when the header has previously been removed'); + } + + public function testGetMaxAge() + { + $response = new Response(); + $response->headers->set('Cache-Control', 's-maxage=600, max-age=0'); + $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() uses s-maxage cache control directive when present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=600'); + $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() falls back to max-age when no s-maxage directive present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'must-revalidate'); + $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822)); + $this->assertEquals(3600, $response->getMaxAge(), '->getMaxAge() falls back to Expires when no max-age or s-maxage directive present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'must-revalidate'); + $response->headers->set('Expires', -1); + $this->assertEquals('Sat, 01 Jan 00 00:00:00 +0000', $response->getExpires()->format(DATE_RFC822)); + + $response = new Response(); + $this->assertNull($response->getMaxAge(), '->getMaxAge() returns null if no freshness information available'); + } + + public function testSetSharedMaxAge() + { + $response = new Response(); + $response->setSharedMaxAge(20); + + $cacheControl = $response->headers->get('Cache-Control'); + $this->assertEquals('public, s-maxage=20', $cacheControl); + } + + public function testIsPrivate() + { + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=100'); + $response->setPrivate(); + $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true'); + $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'public, max-age=100'); + $response->setPrivate(); + $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true'); + $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true'); + $this->assertFalse($response->headers->hasCacheControlDirective('public'), '->isPrivate() removes the public Cache-Control directive'); + } + + public function testExpire() + { + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=100'); + $response->expire(); + $this->assertEquals(100, $response->headers->get('Age'), '->expire() sets the Age to max-age when present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=100, s-maxage=500'); + $response->expire(); + $this->assertEquals(500, $response->headers->get('Age'), '->expire() sets the Age to s-maxage when both max-age and s-maxage are present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=5, s-maxage=500'); + $response->headers->set('Age', '1000'); + $response->expire(); + $this->assertEquals(1000, $response->headers->get('Age'), '->expire() does nothing when the response is already stale/expired'); + + $response = new Response(); + $response->expire(); + $this->assertFalse($response->headers->has('Age'), '->expire() does nothing when the response does not include freshness information'); + + $response = new Response(); + $response->headers->set('Expires', -1); + $response->expire(); + $this->assertNull($response->headers->get('Age'), '->expire() does not set the Age when the response is expired'); + } + + public function testGetTtl() + { + $response = new Response(); + $this->assertNull($response->getTtl(), '->getTtl() returns null when no Expires or Cache-Control headers are present'); + + $response = new Response(); + $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822)); + $this->assertEquals(3600, $response->getTtl(), '->getTtl() uses the Expires header when no max-age is present'); + + $response = new Response(); + $response->headers->set('Expires', $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)); + $this->assertLessThan(0, $response->getTtl(), '->getTtl() returns negative values when Expires is in past'); + + $response = new Response(); + $response->headers->set('Expires', $response->getDate()->format(DATE_RFC2822)); + $response->headers->set('Age', 0); + $this->assertSame(0, $response->getTtl(), '->getTtl() correctly handles zero'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=60'); + $this->assertEquals(60, $response->getTtl(), '->getTtl() uses Cache-Control max-age when present'); + } + + public function testSetClientTtl() + { + $response = new Response(); + $response->setClientTtl(10); + + $this->assertEquals($response->getMaxAge(), $response->getAge() + 10); + } + + public function testGetSetProtocolVersion() + { + $response = new Response(); + + $this->assertEquals('1.0', $response->getProtocolVersion()); + + $response->setProtocolVersion('1.1'); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + } + + public function testGetVary() + { + $response = new Response(); + $this->assertEquals(array(), $response->getVary(), '->getVary() returns an empty array if no Vary header is present'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language'); + $this->assertEquals(array('Accept-Language'), $response->getVary(), '->getVary() parses a single header name value'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language User-Agent X-Foo'); + $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by spaces'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language,User-Agent, X-Foo'); + $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by commas'); + + $vary = array('Accept-Language', 'User-Agent', 'X-foo'); + + $response = new Response(); + $response->headers->set('Vary', $vary); + $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language, User-Agent, X-foo'); + $this->assertEquals($vary, $response->getVary(), '->getVary() parses multiple header name values in arrays'); + } + + public function testSetVary() + { + $response = new Response(); + $response->setVary('Accept-Language'); + $this->assertEquals(array('Accept-Language'), $response->getVary()); + + $response->setVary('Accept-Language, User-Agent'); + $this->assertEquals(array('Accept-Language', 'User-Agent'), $response->getVary(), '->setVary() replace the vary header by default'); + + $response->setVary('X-Foo', false); + $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->setVary() doesn\'t wipe out earlier Vary headers if replace is set to false'); + } + + public function testDefaultContentType() + { + $headerMock = $this->getMockBuilder('Symfony\Component\HttpFoundation\ResponseHeaderBag')->setMethods(array('set'))->getMock(); + $headerMock->expects($this->at(0)) + ->method('set') + ->with('Content-Type', 'text/html'); + $headerMock->expects($this->at(1)) + ->method('set') + ->with('Content-Type', 'text/html; charset=UTF-8'); + + $response = new Response('foo'); + $response->headers = $headerMock; + + $response->prepare(new Request()); + } + + public function testContentTypeCharset() + { + $response = new Response(); + $response->headers->set('Content-Type', 'text/css'); + + // force fixContentType() to be called + $response->prepare(new Request()); + + $this->assertEquals('text/css; charset=UTF-8', $response->headers->get('Content-Type')); + } + + public function testPrepareDoesNothingIfContentTypeIsSet() + { + $response = new Response('foo'); + $response->headers->set('Content-Type', 'text/plain'); + + $response->prepare(new Request()); + + $this->assertEquals('text/plain; charset=UTF-8', $response->headers->get('content-type')); + } + + public function testPrepareDoesNothingIfRequestFormatIsNotDefined() + { + $response = new Response('foo'); + + $response->prepare(new Request()); + + $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('content-type')); + } + + public function testPrepareSetContentType() + { + $response = new Response('foo'); + $request = Request::create('/'); + $request->setRequestFormat('json'); + + $response->prepare($request); + + $this->assertEquals('application/json', $response->headers->get('content-type')); + } + + public function testPrepareRemovesContentForHeadRequests() + { + $response = new Response('foo'); + $request = Request::create('/', 'HEAD'); + + $length = 12345; + $response->headers->set('Content-Length', $length); + $response->prepare($request); + + $this->assertEquals('', $response->getContent()); + $this->assertEquals($length, $response->headers->get('Content-Length'), 'Content-Length should be as if it was GET; see RFC2616 14.13'); + } + + public function testPrepareRemovesContentForInformationalResponse() + { + $response = new Response('foo'); + $request = Request::create('/'); + + $response->setContent('content'); + $response->setStatusCode(101); + $response->prepare($request); + $this->assertEquals('', $response->getContent()); + $this->assertFalse($response->headers->has('Content-Type')); + $this->assertFalse($response->headers->has('Content-Type')); + + $response->setContent('content'); + $response->setStatusCode(304); + $response->prepare($request); + $this->assertEquals('', $response->getContent()); + $this->assertFalse($response->headers->has('Content-Type')); + $this->assertFalse($response->headers->has('Content-Length')); + } + + public function testPrepareRemovesContentLength() + { + $response = new Response('foo'); + $request = Request::create('/'); + + $response->headers->set('Content-Length', 12345); + $response->prepare($request); + $this->assertEquals(12345, $response->headers->get('Content-Length')); + + $response->headers->set('Transfer-Encoding', 'chunked'); + $response->prepare($request); + $this->assertFalse($response->headers->has('Content-Length')); + } + + public function testPrepareSetsPragmaOnHttp10Only() + { + $request = Request::create('/', 'GET'); + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0'); + + $response = new Response('foo'); + $response->prepare($request); + $this->assertEquals('no-cache', $response->headers->get('pragma')); + $this->assertEquals('-1', $response->headers->get('expires')); + + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1'); + $response = new Response('foo'); + $response->prepare($request); + $this->assertFalse($response->headers->has('pragma')); + $this->assertFalse($response->headers->has('expires')); + } + + public function testSetCache() + { + $response = new Response(); + //array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public') + try { + $response->setCache(array('wrong option' => 'value')); + $this->fail('->setCache() throws an InvalidArgumentException if an option is not supported'); + } catch (\Exception $e) { + $this->assertInstanceOf('InvalidArgumentException', $e, '->setCache() throws an InvalidArgumentException if an option is not supported'); + $this->assertContains('"wrong option"', $e->getMessage()); + } + + $options = array('etag' => '"whatever"'); + $response->setCache($options); + $this->assertEquals($response->getEtag(), '"whatever"'); + + $now = $this->createDateTimeNow(); + $options = array('last_modified' => $now); + $response->setCache($options); + $this->assertEquals($response->getLastModified()->getTimestamp(), $now->getTimestamp()); + + $options = array('max_age' => 100); + $response->setCache($options); + $this->assertEquals($response->getMaxAge(), 100); + + $options = array('s_maxage' => 200); + $response->setCache($options); + $this->assertEquals($response->getMaxAge(), 200); + + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('public' => true)); + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('public' => false)); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('private' => true)); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('private' => false)); + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + } + + public function testSendContent() + { + $response = new Response('test response rendering', 200); + + ob_start(); + $response->sendContent(); + $string = ob_get_clean(); + $this->assertContains('test response rendering', $string); + } + + public function testSetPublic() + { + $response = new Response(); + $response->setPublic(); + + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + } + + public function testSetExpires() + { + $response = new Response(); + $response->setExpires(null); + + $this->assertNull($response->getExpires(), '->setExpires() remove the header when passed null'); + + $now = $this->createDateTimeNow(); + $response->setExpires($now); + + $this->assertEquals($response->getExpires()->getTimestamp(), $now->getTimestamp()); + } + + public function testSetLastModified() + { + $response = new Response(); + $response->setLastModified($this->createDateTimeNow()); + $this->assertNotNull($response->getLastModified()); + + $response->setLastModified(null); + $this->assertNull($response->getLastModified()); + } + + public function testIsInvalid() + { + $response = new Response(); + + try { + $response->setStatusCode(99); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertTrue($response->isInvalid()); + } + + try { + $response->setStatusCode(650); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertTrue($response->isInvalid()); + } + + $response = new Response('', 200); + $this->assertFalse($response->isInvalid()); + } + + /** + * @dataProvider getStatusCodeFixtures + */ + public function testSetStatusCode($code, $text, $expectedText) + { + $response = new Response(); + + $response->setStatusCode($code, $text); + + $statusText = new \ReflectionProperty($response, 'statusText'); + $statusText->setAccessible(true); + + $this->assertEquals($expectedText, $statusText->getValue($response)); + } + + public function getStatusCodeFixtures() + { + return array( + array('200', null, 'OK'), + array('200', false, ''), + array('200', 'foo', 'foo'), + array('199', null, 'unknown status'), + array('199', false, ''), + array('199', 'foo', 'foo'), + ); + } + + public function testIsInformational() + { + $response = new Response('', 100); + $this->assertTrue($response->isInformational()); + + $response = new Response('', 200); + $this->assertFalse($response->isInformational()); + } + + public function testIsRedirectRedirection() + { + foreach (array(301, 302, 303, 307) as $code) { + $response = new Response('', $code); + $this->assertTrue($response->isRedirection()); + $this->assertTrue($response->isRedirect()); + } + + $response = new Response('', 304); + $this->assertTrue($response->isRedirection()); + $this->assertFalse($response->isRedirect()); + + $response = new Response('', 200); + $this->assertFalse($response->isRedirection()); + $this->assertFalse($response->isRedirect()); + + $response = new Response('', 404); + $this->assertFalse($response->isRedirection()); + $this->assertFalse($response->isRedirect()); + + $response = new Response('', 301, array('Location' => '/good-uri')); + $this->assertFalse($response->isRedirect('/bad-uri')); + $this->assertTrue($response->isRedirect('/good-uri')); + } + + public function testIsNotFound() + { + $response = new Response('', 404); + $this->assertTrue($response->isNotFound()); + + $response = new Response('', 200); + $this->assertFalse($response->isNotFound()); + } + + public function testIsEmpty() + { + foreach (array(204, 304) as $code) { + $response = new Response('', $code); + $this->assertTrue($response->isEmpty()); + } + + $response = new Response('', 200); + $this->assertFalse($response->isEmpty()); + } + + public function testIsForbidden() + { + $response = new Response('', 403); + $this->assertTrue($response->isForbidden()); + + $response = new Response('', 200); + $this->assertFalse($response->isForbidden()); + } + + public function testIsOk() + { + $response = new Response('', 200); + $this->assertTrue($response->isOk()); + + $response = new Response('', 404); + $this->assertFalse($response->isOk()); + } + + public function testIsServerOrClientError() + { + $response = new Response('', 404); + $this->assertTrue($response->isClientError()); + $this->assertFalse($response->isServerError()); + + $response = new Response('', 500); + $this->assertFalse($response->isClientError()); + $this->assertTrue($response->isServerError()); + } + + public function testHasVary() + { + $response = new Response(); + $this->assertFalse($response->hasVary()); + + $response->setVary('User-Agent'); + $this->assertTrue($response->hasVary()); + } + + public function testSetEtag() + { + $response = new Response('', 200, array('ETag' => '"12345"')); + $response->setEtag(); + + $this->assertNull($response->headers->get('Etag'), '->setEtag() removes Etags when call with null'); + } + + /** + * @dataProvider validContentProvider + */ + public function testSetContent($content) + { + $response = new Response(); + $response->setContent($content); + $this->assertEquals((string) $content, $response->getContent()); + } + + /** + * @expectedException \UnexpectedValueException + * @dataProvider invalidContentProvider + */ + public function testSetContentInvalid($content) + { + $response = new Response(); + $response->setContent($content); + } + + public function testSettersAreChainable() + { + $response = new Response(); + + $setters = array( + 'setProtocolVersion' => '1.0', + 'setCharset' => 'UTF-8', + 'setPublic' => null, + 'setPrivate' => null, + 'setDate' => $this->createDateTimeNow(), + 'expire' => null, + 'setMaxAge' => 1, + 'setSharedMaxAge' => 1, + 'setTtl' => 1, + 'setClientTtl' => 1, + ); + + foreach ($setters as $setter => $arg) { + $this->assertEquals($response, $response->{$setter}($arg)); + } + } + + public function testNoDeprecationsAreTriggered() + { + new DefaultResponse(); + $this->getMockBuilder(Response::class)->getMock(); + + // we just need to ensure that subclasses of Response can be created without any deprecations + // being triggered if the subclass does not override any final methods + $this->addToAssertionCount(1); + } + + public function validContentProvider() + { + return array( + 'obj' => array(new StringableObject()), + 'string' => array('Foo'), + 'int' => array(2), + ); + } + + public function invalidContentProvider() + { + return array( + 'obj' => array(new \stdClass()), + 'array' => array(array()), + 'bool' => array(true, '1'), + ); + } + + protected function createDateTimeOneHourAgo() + { + return $this->createDateTimeNow()->sub(new \DateInterval('PT1H')); + } + + protected function createDateTimeOneHourLater() + { + return $this->createDateTimeNow()->add(new \DateInterval('PT1H')); + } + + protected function createDateTimeNow() + { + $date = new \DateTime(); + + return $date->setTimestamp(time()); + } + + protected function provideResponse() + { + return new Response(); + } + + /** + * @see http://github.com/zendframework/zend-diactoros for the canonical source repository + * + * @author Fábio Pacheco + * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com) + * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License + */ + public function ianaCodesReasonPhrasesProvider() + { + if (!in_array('https', stream_get_wrappers(), true)) { + $this->markTestSkipped('The "https" wrapper is not available'); + } + + $ianaHttpStatusCodes = new \DOMDocument(); + + libxml_set_streams_context(stream_context_create(array( + 'http' => array( + 'method' => 'GET', + 'timeout' => 30, + ), + ))); + + $ianaHttpStatusCodes->load('https://www.iana.org/assignments/http-status-codes/http-status-codes.xml'); + if (!$ianaHttpStatusCodes->relaxNGValidate(__DIR__.'/schema/http-status-codes.rng')) { + self::fail('Invalid IANA\'s HTTP status code list.'); + } + + $ianaCodesReasonPhrases = array(); + + $xpath = new \DomXPath($ianaHttpStatusCodes); + $xpath->registerNamespace('ns', 'http://www.iana.org/assignments'); + + $records = $xpath->query('//ns:record'); + foreach ($records as $record) { + $value = $xpath->query('.//ns:value', $record)->item(0)->nodeValue; + $description = $xpath->query('.//ns:description', $record)->item(0)->nodeValue; + + if (in_array($description, array('Unassigned', '(Unused)'), true)) { + continue; + } + + if (preg_match('/^([0-9]+)\s*\-\s*([0-9]+)$/', $value, $matches)) { + for ($value = $matches[1]; $value <= $matches[2]; ++$value) { + $ianaCodesReasonPhrases[] = array($value, $description); + } + } else { + $ianaCodesReasonPhrases[] = array($value, $description); + } + } + + return $ianaCodesReasonPhrases; + } + + /** + * @dataProvider ianaCodesReasonPhrasesProvider + */ + public function testReasonPhraseDefaultsAgainstIana($code, $reasonPhrase) + { + $this->assertEquals($reasonPhrase, Response::$statusTexts[$code]); + } +} + +class StringableObject +{ + public function __toString() + { + return 'Foo'; + } +} + +class DefaultResponse extends Response +{ +} + +class ExtendedResponse extends Response +{ + public function setLastModified(\DateTime $date = null) + { + } + + public function getDate() + { + } +} diff --git a/vendor/symfony/http-foundation/Tests/ResponseTestCase.php b/vendor/symfony/http-foundation/Tests/ResponseTestCase.php new file mode 100644 index 00000000..4ead34c1 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ResponseTestCase.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; + +abstract class ResponseTestCase extends TestCase +{ + public function testNoCacheControlHeaderOnAttachmentUsingHTTPSAndMSIE() + { + // Check for HTTPS and IE 8 + $request = new Request(); + $request->server->set('HTTPS', true); + $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertFalse($response->headers->has('Cache-Control')); + + // Check for IE 10 and HTTPS + $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for IE 9 and HTTPS + $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for IE 9 and HTTP + $request->server->set('HTTPS', false); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for IE 8 and HTTP + $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for non-IE and HTTPS + $request->server->set('HTTPS', true); + $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for non-IE and HTTP + $request->server->set('HTTPS', false); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + } + + abstract protected function provideResponse(); +} diff --git a/vendor/symfony/http-foundation/Tests/ServerBagTest.php b/vendor/symfony/http-foundation/Tests/ServerBagTest.php new file mode 100644 index 00000000..c1d9d12a --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ServerBagTest.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\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\ServerBag; + +/** + * ServerBagTest. + * + * @author Bulat Shakirzyanov + */ +class ServerBagTest extends TestCase +{ + public function testShouldExtractHeadersFromServerArray() + { + $server = array( + 'SOME_SERVER_VARIABLE' => 'value', + 'SOME_SERVER_VARIABLE2' => 'value', + 'ROOT' => 'value', + 'HTTP_CONTENT_TYPE' => 'text/html', + 'HTTP_CONTENT_LENGTH' => '0', + 'HTTP_ETAG' => 'asdf', + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar', + ); + + $bag = new ServerBag($server); + + $this->assertEquals(array( + 'CONTENT_TYPE' => 'text/html', + 'CONTENT_LENGTH' => '0', + 'ETAG' => 'asdf', + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar', + ), $bag->getHeaders()); + } + + public function testHttpPasswordIsOptional() + { + $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo')); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => '', + ), $bag->getHeaders()); + } + + public function testHttpBasicAuthWithPhpCgi() + { + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'))); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar', + ), $bag->getHeaders()); + } + + public function testHttpBasicAuthWithPhpCgiBogus() + { + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic_'.base64_encode('foo:bar'))); + + // Username and passwords should not be set as the header is bogus + $headers = $bag->getHeaders(); + $this->assertFalse(isset($headers['PHP_AUTH_USER'])); + $this->assertFalse(isset($headers['PHP_AUTH_PW'])); + } + + public function testHttpBasicAuthWithPhpCgiRedirect() + { + $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word'))); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('username:pass:word'), + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'pass:word', + ), $bag->getHeaders()); + } + + public function testHttpBasicAuthWithPhpCgiEmptyPassword() + { + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:'))); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => '', + ), $bag->getHeaders()); + } + + public function testHttpDigestAuthWithPhpCgi() + { + $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"'; + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $digest, + 'PHP_AUTH_DIGEST' => $digest, + ), $bag->getHeaders()); + } + + public function testHttpDigestAuthWithPhpCgiBogus() + { + $digest = 'Digest_username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"'; + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $digest)); + + // Username and passwords should not be set as the header is bogus + $headers = $bag->getHeaders(); + $this->assertFalse(isset($headers['PHP_AUTH_USER'])); + $this->assertFalse(isset($headers['PHP_AUTH_PW'])); + } + + public function testHttpDigestAuthWithPhpCgiRedirect() + { + $digest = 'Digest username="foo", realm="acme", nonce="'.md5('secret').'", uri="/protected, qop="auth"'; + $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $digest)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $digest, + 'PHP_AUTH_DIGEST' => $digest, + ), $bag->getHeaders()); + } + + public function testOAuthBearerAuth() + { + $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo'; + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $headerContent)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $headerContent, + ), $bag->getHeaders()); + } + + public function testOAuthBearerAuthWithRedirect() + { + $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo'; + $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => $headerContent)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $headerContent, + ), $bag->getHeaders()); + } + + /** + * @see https://github.com/symfony/symfony/issues/17345 + */ + public function testItDoesNotOverwriteTheAuthorizationHeaderIfItIsAlreadySet() + { + $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo'; + $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo', 'HTTP_AUTHORIZATION' => $headerContent)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $headerContent, + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => '', + ), $bag->getHeaders()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php new file mode 100644 index 00000000..8c148b58 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Tests AttributeBag. + * + * @author Drak + */ +class AttributeBagTest extends TestCase +{ + /** + * @var array + */ + private $array; + + /** + * @var AttributeBag + */ + private $bag; + + protected function setUp() + { + $this->array = array( + 'hello' => 'world', + 'always' => 'be happy', + 'user.login' => 'drak', + 'csrf.token' => array( + 'a' => '1234', + 'b' => '4321', + ), + 'category' => array( + 'fishing' => array( + 'first' => 'cod', + 'second' => 'sole', + ), + ), + ); + $this->bag = new AttributeBag('_sf2'); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + $this->array = array(); + } + + public function testInitialize() + { + $bag = new AttributeBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $bag->all()); + $array = array('should' => 'change'); + $bag->initialize($array); + $this->assertEquals($array, $bag->all()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2', $this->bag->getStorageKey()); + $attributeBag = new AttributeBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + public function testGetSetName() + { + $this->assertEquals('attributes', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + /** + * @dataProvider attributesProvider + */ + public function testHas($key, $value, $exists) + { + $this->assertEquals($exists, $this->bag->has($key)); + } + + /** + * @dataProvider attributesProvider + */ + public function testGet($key, $value, $expected) + { + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testGetDefaults() + { + $this->assertNull($this->bag->get('user2.login')); + $this->assertEquals('default', $this->bag->get('user2.login', 'default')); + } + + /** + * @dataProvider attributesProvider + */ + public function testSet($key, $value, $expected) + { + $this->bag->set($key, $value); + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testAll() + { + $this->assertEquals($this->array, $this->bag->all()); + + $this->bag->set('hello', 'fabien'); + $array = $this->array; + $array['hello'] = 'fabien'; + $this->assertEquals($array, $this->bag->all()); + } + + public function testReplace() + { + $array = array(); + $array['name'] = 'jack'; + $array['foo.bar'] = 'beep'; + $this->bag->replace($array); + $this->assertEquals($array, $this->bag->all()); + $this->assertNull($this->bag->get('hello')); + $this->assertNull($this->bag->get('always')); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemove() + { + $this->assertEquals('world', $this->bag->get('hello')); + $this->bag->remove('hello'); + $this->assertNull($this->bag->get('hello')); + + $this->assertEquals('be happy', $this->bag->get('always')); + $this->bag->remove('always'); + $this->assertNull($this->bag->get('always')); + + $this->assertEquals('drak', $this->bag->get('user.login')); + $this->bag->remove('user.login'); + $this->assertNull($this->bag->get('user.login')); + } + + public function testClear() + { + $this->bag->clear(); + $this->assertEquals(array(), $this->bag->all()); + } + + public function attributesProvider() + { + return array( + array('hello', 'world', true), + array('always', 'be happy', true), + array('user.login', 'drak', true), + array('csrf.token', array('a' => '1234', 'b' => '4321'), true), + array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true), + array('user2.login', null, false), + array('never', null, false), + array('bye', null, false), + array('bye/for/now', null, false), + ); + } + + public function testGetIterator() + { + $i = 0; + foreach ($this->bag as $key => $val) { + $this->assertEquals($this->array[$key], $val); + ++$i; + } + + $this->assertEquals(count($this->array), $i); + } + + public function testCount() + { + $this->assertEquals(count($this->array), count($this->bag)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php new file mode 100644 index 00000000..d9d9eb7f --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag; + +/** + * Tests NamespacedAttributeBag. + * + * @author Drak + */ +class NamespacedAttributeBagTest extends TestCase +{ + /** + * @var array + */ + private $array; + + /** + * @var NamespacedAttributeBag + */ + private $bag; + + protected function setUp() + { + $this->array = array( + 'hello' => 'world', + 'always' => 'be happy', + 'user.login' => 'drak', + 'csrf.token' => array( + 'a' => '1234', + 'b' => '4321', + ), + 'category' => array( + 'fishing' => array( + 'first' => 'cod', + 'second' => 'sole', + ), + ), + ); + $this->bag = new NamespacedAttributeBag('_sf2', '/'); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + $this->array = array(); + } + + public function testInitialize() + { + $bag = new NamespacedAttributeBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $this->bag->all()); + $array = array('should' => 'not stick'); + $bag->initialize($array); + + // should have remained the same + $this->assertEquals($this->array, $this->bag->all()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2', $this->bag->getStorageKey()); + $attributeBag = new NamespacedAttributeBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + /** + * @dataProvider attributesProvider + */ + public function testHas($key, $value, $exists) + { + $this->assertEquals($exists, $this->bag->has($key)); + } + + /** + * @dataProvider attributesProvider + */ + public function testGet($key, $value, $expected) + { + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testGetDefaults() + { + $this->assertNull($this->bag->get('user2.login')); + $this->assertEquals('default', $this->bag->get('user2.login', 'default')); + } + + /** + * @dataProvider attributesProvider + */ + public function testSet($key, $value, $expected) + { + $this->bag->set($key, $value); + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testAll() + { + $this->assertEquals($this->array, $this->bag->all()); + + $this->bag->set('hello', 'fabien'); + $array = $this->array; + $array['hello'] = 'fabien'; + $this->assertEquals($array, $this->bag->all()); + } + + public function testReplace() + { + $array = array(); + $array['name'] = 'jack'; + $array['foo.bar'] = 'beep'; + $this->bag->replace($array); + $this->assertEquals($array, $this->bag->all()); + $this->assertNull($this->bag->get('hello')); + $this->assertNull($this->bag->get('always')); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemove() + { + $this->assertEquals('world', $this->bag->get('hello')); + $this->bag->remove('hello'); + $this->assertNull($this->bag->get('hello')); + + $this->assertEquals('be happy', $this->bag->get('always')); + $this->bag->remove('always'); + $this->assertNull($this->bag->get('always')); + + $this->assertEquals('drak', $this->bag->get('user.login')); + $this->bag->remove('user.login'); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemoveExistingNamespacedAttribute() + { + $this->assertSame('cod', $this->bag->remove('category/fishing/first')); + } + + public function testRemoveNonexistingNamespacedAttribute() + { + $this->assertNull($this->bag->remove('foo/bar/baz')); + } + + public function testClear() + { + $this->bag->clear(); + $this->assertEquals(array(), $this->bag->all()); + } + + public function attributesProvider() + { + return array( + array('hello', 'world', true), + array('always', 'be happy', true), + array('user.login', 'drak', true), + array('csrf.token', array('a' => '1234', 'b' => '4321'), true), + array('csrf.token/a', '1234', true), + array('csrf.token/b', '4321', true), + array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true), + array('category/fishing', array('first' => 'cod', 'second' => 'sole'), true), + array('category/fishing/missing/first', null, false), + array('category/fishing/first', 'cod', true), + array('category/fishing/second', 'sole', true), + array('category/fishing/missing/second', null, false), + array('user2.login', null, false), + array('never', null, false), + array('bye', null, false), + array('bye/for/now', null, false), + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php new file mode 100644 index 00000000..4eb200af --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.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\HttpFoundation\Tests\Session\Flash; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag as FlashBag; + +/** + * AutoExpireFlashBagTest. + * + * @author Drak + */ +class AutoExpireFlashBagTest extends TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + protected function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array('new' => array('notice' => array('A previous flash message'))); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $array = array('new' => array('notice' => array('A previous flash message'))); + $bag->initialize($array); + $this->assertEquals(array('A previous flash message'), $bag->peek('notice')); + $array = array('new' => array( + 'notice' => array('Something else'), + 'error' => array('a'), + )); + $bag->initialize($array); + $this->assertEquals(array('Something else'), $bag->peek('notice')); + $this->assertEquals(array('a'), $bag->peek('error')); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2_flashes', $this->bag->getStorageKey()); + $attributeBag = new FlashBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + public function testGetSetName() + { + $this->assertEquals('flashes', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + public function testPeek() + { + $this->assertEquals(array(), $this->bag->peek('non_existing')); + $this->assertEquals(array('default'), $this->bag->peek('non_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + } + + public function testSet() + { + $this->bag->set('notice', 'Foo'); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has('notice')); + } + + public function testKeys() + { + $this->assertEquals(array('notice'), $this->bag->keys()); + } + + public function testPeekAll() + { + $array = array( + 'new' => array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), + ); + + $this->bag->initialize($array); + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + } + + public function testGet() + { + $this->assertEquals(array(), $this->bag->get('non_existing')); + $this->assertEquals(array('default'), $this->bag->get('non_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->get('notice')); + $this->assertEquals(array(), $this->bag->get('notice')); + } + + public function testSetAll() + { + $this->bag->setAll(array('a' => 'first', 'b' => 'second')); + $this->assertFalse($this->bag->has('a')); + $this->assertFalse($this->bag->has('b')); + } + + public function testAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => array('A previous flash message'), + ), $this->bag->all() + ); + + $this->assertEquals(array(), $this->bag->all()); + } + + public function testClear() + { + $this->assertEquals(array('notice' => array('A previous flash message')), $this->bag->clear()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php new file mode 100644 index 00000000..f0aa6a61 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Flash; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; + +/** + * FlashBagTest. + * + * @author Drak + */ +class FlashBagTest extends TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + protected function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array('notice' => array('A previous flash message')); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $bag->peekAll()); + $array = array('should' => array('change')); + $bag->initialize($array); + $this->assertEquals($array, $bag->peekAll()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2_flashes', $this->bag->getStorageKey()); + $attributeBag = new FlashBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + public function testGetSetName() + { + $this->assertEquals('flashes', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + public function testPeek() + { + $this->assertEquals(array(), $this->bag->peek('non_existing')); + $this->assertEquals(array('default'), $this->bag->peek('not_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + } + + public function testGet() + { + $this->assertEquals(array(), $this->bag->get('non_existing')); + $this->assertEquals(array('default'), $this->bag->get('not_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->get('notice')); + $this->assertEquals(array(), $this->bag->get('notice')); + } + + public function testAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => array('Foo'), + 'error' => array('Bar'), ), $this->bag->all() + ); + + $this->assertEquals(array(), $this->bag->all()); + } + + public function testSet() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('notice', 'Bar'); + $this->assertEquals(array('Bar'), $this->bag->peek('notice')); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has('notice')); + } + + public function testKeys() + { + $this->assertEquals(array('notice'), $this->bag->keys()); + } + + public function testPeekAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => array('Foo'), + 'error' => array('Bar'), + ), $this->bag->peekAll() + ); + $this->assertTrue($this->bag->has('notice')); + $this->assertTrue($this->bag->has('error')); + $this->assertEquals(array( + 'notice' => array('Foo'), + 'error' => array('Bar'), + ), $this->bag->peekAll() + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/SessionTest.php b/vendor/symfony/http-foundation/Tests/Session/SessionTest.php new file mode 100644 index 00000000..fa93507a --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/SessionTest.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; + +/** + * SessionTest. + * + * @author Fabien Potencier + * @author Robert Schönthal + * @author Drak + */ +class SessionTest extends TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface + */ + protected $storage; + + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + protected function setUp() + { + $this->storage = new MockArraySessionStorage(); + $this->session = new Session($this->storage, new AttributeBag(), new FlashBag()); + } + + protected function tearDown() + { + $this->storage = null; + $this->session = null; + } + + public function testStart() + { + $this->assertEquals('', $this->session->getId()); + $this->assertTrue($this->session->start()); + $this->assertNotEquals('', $this->session->getId()); + } + + public function testIsStarted() + { + $this->assertFalse($this->session->isStarted()); + $this->session->start(); + $this->assertTrue($this->session->isStarted()); + } + + public function testSetId() + { + $this->assertEquals('', $this->session->getId()); + $this->session->setId('0123456789abcdef'); + $this->session->start(); + $this->assertEquals('0123456789abcdef', $this->session->getId()); + } + + public function testSetName() + { + $this->assertEquals('MOCKSESSID', $this->session->getName()); + $this->session->setName('session.test.com'); + $this->session->start(); + $this->assertEquals('session.test.com', $this->session->getName()); + } + + public function testGet() + { + // tests defaults + $this->assertNull($this->session->get('foo')); + $this->assertEquals(1, $this->session->get('foo', 1)); + } + + /** + * @dataProvider setProvider + */ + public function testSet($key, $value) + { + $this->session->set($key, $value); + $this->assertEquals($value, $this->session->get($key)); + } + + /** + * @dataProvider setProvider + */ + public function testHas($key, $value) + { + $this->session->set($key, $value); + $this->assertTrue($this->session->has($key)); + $this->assertFalse($this->session->has($key.'non_value')); + } + + public function testReplace() + { + $this->session->replace(array('happiness' => 'be good', 'symfony' => 'awesome')); + $this->assertEquals(array('happiness' => 'be good', 'symfony' => 'awesome'), $this->session->all()); + $this->session->replace(array()); + $this->assertEquals(array(), $this->session->all()); + } + + /** + * @dataProvider setProvider + */ + public function testAll($key, $value, $result) + { + $this->session->set($key, $value); + $this->assertEquals($result, $this->session->all()); + } + + /** + * @dataProvider setProvider + */ + public function testClear($key, $value) + { + $this->session->set('hi', 'fabien'); + $this->session->set($key, $value); + $this->session->clear(); + $this->assertEquals(array(), $this->session->all()); + } + + public function setProvider() + { + return array( + array('foo', 'bar', array('foo' => 'bar')), + array('foo.bar', 'too much beer', array('foo.bar' => 'too much beer')), + array('great', 'symfony is great', array('great' => 'symfony is great')), + ); + } + + /** + * @dataProvider setProvider + */ + public function testRemove($key, $value) + { + $this->session->set('hi.world', 'have a nice day'); + $this->session->set($key, $value); + $this->session->remove($key); + $this->assertEquals(array('hi.world' => 'have a nice day'), $this->session->all()); + } + + public function testInvalidate() + { + $this->session->set('invalidate', 123); + $this->session->invalidate(); + $this->assertEquals(array(), $this->session->all()); + } + + public function testMigrate() + { + $this->session->set('migrate', 321); + $this->session->migrate(); + $this->assertEquals(321, $this->session->get('migrate')); + } + + public function testMigrateDestroy() + { + $this->session->set('migrate', 333); + $this->session->migrate(true); + $this->assertEquals(333, $this->session->get('migrate')); + } + + public function testSave() + { + $this->session->start(); + $this->session->save(); + + $this->assertFalse($this->session->isStarted()); + } + + public function testGetId() + { + $this->assertEquals('', $this->session->getId()); + $this->session->start(); + $this->assertNotEquals('', $this->session->getId()); + } + + public function testGetFlashBag() + { + $this->assertInstanceOf('Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface', $this->session->getFlashBag()); + } + + public function testGetIterator() + { + $attributes = array('hello' => 'world', 'symfony' => 'rocks'); + foreach ($attributes as $key => $val) { + $this->session->set($key, $val); + } + + $i = 0; + foreach ($this->session as $key => $val) { + $this->assertEquals($attributes[$key], $val); + ++$i; + } + + $this->assertEquals(count($attributes), $i); + } + + public function testGetCount() + { + $this->session->set('hello', 'world'); + $this->session->set('symfony', 'rocks'); + + $this->assertCount(2, $this->session); + } + + public function testGetMeta() + { + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', $this->session->getMetadataBag()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php new file mode 100644 index 00000000..06193c8b --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcacheSessionHandler; + +/** + * @requires extension memcache + * @group time-sensitive + */ +class MemcacheSessionHandlerTest extends TestCase +{ + const PREFIX = 'prefix_'; + const TTL = 1000; + /** + * @var MemcacheSessionHandler + */ + protected $storage; + + protected $memcache; + + protected function setUp() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcache class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); + } + + parent::setUp(); + $this->memcache = $this->getMockBuilder('Memcache')->getMock(); + $this->storage = new MemcacheSessionHandler( + $this->memcache, + array('prefix' => self::PREFIX, 'expiretime' => self::TTL) + ); + } + + protected function tearDown() + { + $this->memcache = null; + $this->storage = null; + parent::tearDown(); + } + + public function testOpenSession() + { + $this->assertTrue($this->storage->open('', '')); + } + + public function testCloseSession() + { + $this->assertTrue($this->storage->close()); + } + + public function testReadSession() + { + $this->memcache + ->expects($this->once()) + ->method('get') + ->with(self::PREFIX.'id') + ; + + $this->assertEquals('', $this->storage->read('id')); + } + + public function testWriteSession() + { + $this->memcache + ->expects($this->once()) + ->method('set') + ->with(self::PREFIX.'id', 'data', 0, $this->equalTo(time() + self::TTL, 2)) + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->write('id', 'data')); + } + + public function testDestroySession() + { + $this->memcache + ->expects($this->once()) + ->method('delete') + ->with(self::PREFIX.'id') + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->destroy('id')); + } + + public function testGcSession() + { + $this->assertTrue($this->storage->gc(123)); + } + + /** + * @dataProvider getOptionFixtures + */ + public function testSupportedOptions($options, $supported) + { + try { + new MemcacheSessionHandler($this->memcache, $options); + $this->assertTrue($supported); + } catch (\InvalidArgumentException $e) { + $this->assertFalse($supported); + } + } + + public function getOptionFixtures() + { + return array( + array(array('prefix' => 'session'), true), + array(array('expiretime' => 100), true), + array(array('prefix' => 'session', 'expiretime' => 200), true), + array(array('expiretime' => 100, 'foo' => 'bar'), false), + ); + } + + public function testGetConnection() + { + $method = new \ReflectionMethod($this->storage, 'getMemcache'); + $method->setAccessible(true); + + $this->assertInstanceOf('\Memcache', $method->invoke($this->storage)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php new file mode 100644 index 00000000..2e7be359 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler; + +/** + * @requires extension memcached + * @group time-sensitive + */ +class MemcachedSessionHandlerTest extends TestCase +{ + const PREFIX = 'prefix_'; + const TTL = 1000; + + /** + * @var MemcachedSessionHandler + */ + protected $storage; + + protected $memcached; + + protected function setUp() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('PHPUnit_MockObject cannot mock the Memcached class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); + } + + parent::setUp(); + + if (version_compare(phpversion('memcached'), '2.2.0', '>=') && version_compare(phpversion('memcached'), '3.0.0b1', '<')) { + $this->markTestSkipped('Tests can only be run with memcached extension 2.1.0 or lower, or 3.0.0b1 or higher'); + } + + $this->memcached = $this->getMockBuilder('Memcached')->getMock(); + $this->storage = new MemcachedSessionHandler( + $this->memcached, + array('prefix' => self::PREFIX, 'expiretime' => self::TTL) + ); + } + + protected function tearDown() + { + $this->memcached = null; + $this->storage = null; + parent::tearDown(); + } + + public function testOpenSession() + { + $this->assertTrue($this->storage->open('', '')); + } + + public function testCloseSession() + { + $this->assertTrue($this->storage->close()); + } + + public function testReadSession() + { + $this->memcached + ->expects($this->once()) + ->method('get') + ->with(self::PREFIX.'id') + ; + + $this->assertEquals('', $this->storage->read('id')); + } + + public function testWriteSession() + { + $this->memcached + ->expects($this->once()) + ->method('set') + ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL, 2)) + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->write('id', 'data')); + } + + public function testDestroySession() + { + $this->memcached + ->expects($this->once()) + ->method('delete') + ->with(self::PREFIX.'id') + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->destroy('id')); + } + + public function testGcSession() + { + $this->assertTrue($this->storage->gc(123)); + } + + /** + * @dataProvider getOptionFixtures + */ + public function testSupportedOptions($options, $supported) + { + try { + new MemcachedSessionHandler($this->memcached, $options); + $this->assertTrue($supported); + } catch (\InvalidArgumentException $e) { + $this->assertFalse($supported); + } + } + + public function getOptionFixtures() + { + return array( + array(array('prefix' => 'session'), true), + array(array('expiretime' => 100), true), + array(array('prefix' => 'session', 'expiretime' => 200), true), + array(array('expiretime' => 100, 'foo' => 'bar'), false), + ); + } + + public function testGetConnection() + { + $method = new \ReflectionMethod($this->storage, 'getMemcached'); + $method->setAccessible(true); + + $this->assertInstanceOf('\Memcached', $method->invoke($this->storage)); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php new file mode 100644 index 00000000..74366863 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -0,0 +1,332 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler; + +/** + * @author Markus Bachmann + * @group time-sensitive + */ +class MongoDbSessionHandlerTest extends TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $mongo; + private $storage; + public $options; + + protected function setUp() + { + parent::setUp(); + + if (extension_loaded('mongodb')) { + if (!class_exists('MongoDB\Client')) { + $this->markTestSkipped('The mongodb/mongodb package is required.'); + } + } elseif (!extension_loaded('mongo')) { + $this->markTestSkipped('The Mongo or MongoDB extension is required.'); + } + + if (phpversion('mongodb')) { + $mongoClass = 'MongoDB\Client'; + } else { + $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient'; + } + + $this->mongo = $this->getMockBuilder($mongoClass) + ->disableOriginalConstructor() + ->getMock(); + + $this->options = array( + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => 'expires_at', + 'database' => 'sf2-test', + 'collection' => 'session-test', + ); + + $this->storage = new MongoDbSessionHandler($this->mongo, $this->options); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidMongo() + { + new MongoDbSessionHandler(new \stdClass(), $this->options); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForMissingOptions() + { + new MongoDbSessionHandler($this->mongo, array()); + } + + public function testOpenMethodAlwaysReturnTrue() + { + $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true'); + } + + public function testCloseMethodAlwaysReturnTrue() + { + $this->assertTrue($this->storage->close(), 'The "close" method should always return true'); + } + + public function testRead() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + // defining the timeout before the actual method call + // allows to test for "greater than" values in the $criteria + $testTimeout = time() + 1; + + $collection->expects($this->once()) + ->method('findOne') + ->will($this->returnCallback(function ($criteria) use ($testTimeout) { + $this->assertArrayHasKey($this->options['id_field'], $criteria); + $this->assertEquals($criteria[$this->options['id_field']], 'foo'); + + $this->assertArrayHasKey($this->options['expiry_field'], $criteria); + $this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]); + + if (phpversion('mongodb')) { + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$gte']); + $this->assertGreaterThanOrEqual(round((string) $criteria[$this->options['expiry_field']]['$gte'] / 1000), $testTimeout); + } else { + $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$gte']); + $this->assertGreaterThanOrEqual($criteria[$this->options['expiry_field']]['$gte']->sec, $testTimeout); + } + + $fields = array( + $this->options['id_field'] => 'foo', + ); + + if (phpversion('mongodb')) { + $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY); + $fields[$this->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000); + } else { + $fields[$this->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY); + $fields[$this->options['id_field']] = new \MongoDate(); + } + + return $fields; + })); + + $this->assertEquals('bar', $this->storage->read('foo')); + } + + public function testWrite() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $data = array(); + + $methodName = phpversion('mongodb') ? 'updateOne' : 'update'; + + $collection->expects($this->once()) + ->method($methodName) + ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) { + $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria); + + if (phpversion('mongodb')) { + $this->assertEquals(array('upsert' => true), $options); + } else { + $this->assertEquals(array('upsert' => true, 'multiple' => false), $options); + } + + $data = $updateData['$set']; + })); + + $expectedExpiry = time() + (int) ini_get('session.gc_maxlifetime'); + $this->assertTrue($this->storage->write('foo', 'bar')); + + if (phpversion('mongodb')) { + $this->assertEquals('bar', $data[$this->options['data_field']]->getData()); + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]); + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]); + $this->assertGreaterThanOrEqual($expectedExpiry, round((string) $data[$this->options['expiry_field']] / 1000)); + } else { + $this->assertEquals('bar', $data[$this->options['data_field']]->bin); + $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]); + $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]); + $this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec); + } + } + + public function testWriteWhenUsingExpiresField() + { + $this->options = array( + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'database' => 'sf2-test', + 'collection' => 'session-test', + 'expiry_field' => 'expiresAt', + ); + + $this->storage = new MongoDbSessionHandler($this->mongo, $this->options); + + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $data = array(); + + $methodName = phpversion('mongodb') ? 'updateOne' : 'update'; + + $collection->expects($this->once()) + ->method($methodName) + ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) { + $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria); + + if (phpversion('mongodb')) { + $this->assertEquals(array('upsert' => true), $options); + } else { + $this->assertEquals(array('upsert' => true, 'multiple' => false), $options); + } + + $data = $updateData['$set']; + })); + + $this->assertTrue($this->storage->write('foo', 'bar')); + + if (phpversion('mongodb')) { + $this->assertEquals('bar', $data[$this->options['data_field']]->getData()); + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]); + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]); + } else { + $this->assertEquals('bar', $data[$this->options['data_field']]->bin); + $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]); + $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]); + } + } + + public function testReplaceSessionData() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $data = array(); + + $methodName = phpversion('mongodb') ? 'updateOne' : 'update'; + + $collection->expects($this->exactly(2)) + ->method($methodName) + ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) { + $data = $updateData; + })); + + $this->storage->write('foo', 'bar'); + $this->storage->write('foo', 'foobar'); + + if (phpversion('mongodb')) { + $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData()); + } else { + $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin); + } + } + + public function testDestroy() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $methodName = phpversion('mongodb') ? 'deleteOne' : 'remove'; + + $collection->expects($this->once()) + ->method($methodName) + ->with(array($this->options['id_field'] => 'foo')); + + $this->assertTrue($this->storage->destroy('foo')); + } + + public function testGc() + { + $collection = $this->createMongoCollectionMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $methodName = phpversion('mongodb') ? 'deleteOne' : 'remove'; + + $collection->expects($this->once()) + ->method($methodName) + ->will($this->returnCallback(function ($criteria) { + if (phpversion('mongodb')) { + $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$lt']); + $this->assertGreaterThanOrEqual(time() - 1, round((string) $criteria[$this->options['expiry_field']]['$lt'] / 1000)); + } else { + $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$lt']); + $this->assertGreaterThanOrEqual(time() - 1, $criteria[$this->options['expiry_field']]['$lt']->sec); + } + })); + + $this->assertTrue($this->storage->gc(1)); + } + + public function testGetConnection() + { + $method = new \ReflectionMethod($this->storage, 'getMongo'); + $method->setAccessible(true); + + if (phpversion('mongodb')) { + $mongoClass = 'MongoDB\Client'; + } else { + $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient'; + } + + $this->assertInstanceOf($mongoClass, $method->invoke($this->storage)); + } + + private function createMongoCollectionMock() + { + $collectionClass = 'MongoCollection'; + if (phpversion('mongodb')) { + $collectionClass = 'MongoDB\Collection'; + } + + $collection = $this->getMockBuilder($collectionClass) + ->disableOriginalConstructor() + ->getMock(); + + return $collection; + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php new file mode 100644 index 00000000..a6264e51 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; + +/** + * Test class for NativeFileSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class NativeFileSessionHandlerTest extends TestCase +{ + public function testConstruct() + { + $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler(sys_get_temp_dir())); + + $this->assertEquals('files', $storage->getSaveHandler()->getSaveHandlerName()); + $this->assertEquals('user', ini_get('session.save_handler')); + + $this->assertEquals(sys_get_temp_dir(), ini_get('session.save_path')); + $this->assertEquals('TESTING', ini_get('session.name')); + } + + /** + * @dataProvider savePathDataProvider + */ + public function testConstructSavePath($savePath, $expectedSavePath, $path) + { + $handler = new NativeFileSessionHandler($savePath); + $this->assertEquals($expectedSavePath, ini_get('session.save_path')); + $this->assertTrue(is_dir(realpath($path))); + + rmdir($path); + } + + public function savePathDataProvider() + { + $base = sys_get_temp_dir(); + + return array( + array("$base/foo", "$base/foo", "$base/foo"), + array("5;$base/foo", "5;$base/foo", "$base/foo"), + array("5;0600;$base/foo", "5;0600;$base/foo", "$base/foo"), + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructException() + { + $handler = new NativeFileSessionHandler('something;invalid;with;too-many-args'); + } + + public function testConstructDefault() + { + $path = ini_get('session.save_path'); + $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler()); + + $this->assertEquals($path, ini_get('session.save_path')); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php new file mode 100644 index 00000000..5486b2d6 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; + +/** + * Test class for NativeSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class NativeSessionHandlerTest extends TestCase +{ + public function testConstruct() + { + $handler = new NativeSessionHandler(); + + $this->assertTrue($handler instanceof \SessionHandler); + $this->assertTrue($handler instanceof NativeSessionHandler); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php new file mode 100644 index 00000000..718fd0f8 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; + +/** + * Test class for NullSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class NullSessionHandlerTest extends TestCase +{ + public function testSaveHandlers() + { + $storage = $this->getStorage(); + $this->assertEquals('user', ini_get('session.save_handler')); + } + + public function testSession() + { + session_id('nullsessionstorage'); + $storage = $this->getStorage(); + $session = new Session($storage); + $this->assertNull($session->get('something')); + $session->set('something', 'unique'); + $this->assertEquals('unique', $session->get('something')); + } + + public function testNothingIsPersisted() + { + session_id('nullsessionstorage'); + $storage = $this->getStorage(); + $session = new Session($storage); + $session->start(); + $this->assertEquals('nullsessionstorage', $session->getId()); + $this->assertNull($session->get('something')); + } + + public function getStorage() + { + return new NativeSessionStorage(array(), new NullSessionHandler()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php new file mode 100644 index 00000000..a47120f1 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -0,0 +1,370 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + +/** + * @requires extension pdo_sqlite + * @group time-sensitive + */ +class PdoSessionHandlerTest extends TestCase +{ + private $dbFile; + + protected function tearDown() + { + // make sure the temporary database file is deleted when it has been created (even when a test fails) + if ($this->dbFile) { + @unlink($this->dbFile); + } + parent::tearDown(); + } + + protected function getPersistentSqliteDsn() + { + $this->dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions'); + + return 'sqlite:'.$this->dbFile; + } + + protected function getMemorySqlitePdo() + { + $pdo = new \PDO('sqlite::memory:'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $storage = new PdoSessionHandler($pdo); + $storage->createTable(); + + return $pdo; + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testWrongPdoErrMode() + { + $pdo = $this->getMemorySqlitePdo(); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT); + + $storage = new PdoSessionHandler($pdo); + } + + /** + * @expectedException \RuntimeException + */ + public function testInexistentTable() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo(), array('db_table' => 'inexistent_table')); + $storage->open('', 'sid'); + $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + } + + /** + * @expectedException \RuntimeException + */ + public function testCreateTableTwice() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->createTable(); + } + + public function testWithLazyDsnConnection() + { + $dsn = $this->getPersistentSqliteDsn(); + + $storage = new PdoSessionHandler($dsn); + $storage->createTable(); + $storage->open('', 'sid'); + $data = $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + $this->assertSame('', $data, 'New session returns empty string data'); + + $storage->open('', 'sid'); + $data = $storage->read('id'); + $storage->close(); + $this->assertSame('data', $data, 'Written value can be read back correctly'); + } + + public function testWithLazySavePathConnection() + { + $dsn = $this->getPersistentSqliteDsn(); + + // Open is called with what ini_set('session.save_path', $dsn) would mean + $storage = new PdoSessionHandler(null); + $storage->open($dsn, 'sid'); + $storage->createTable(); + $data = $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + $this->assertSame('', $data, 'New session returns empty string data'); + + $storage->open($dsn, 'sid'); + $data = $storage->read('id'); + $storage->close(); + $this->assertSame('data', $data, 'Written value can be read back correctly'); + } + + public function testReadWriteReadWithNullByte() + { + $sessionData = 'da'."\0".'ta'; + + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->open('', 'sid'); + $readData = $storage->read('id'); + $storage->write('id', $sessionData); + $storage->close(); + $this->assertSame('', $readData, 'New session returns empty string data'); + + $storage->open('', 'sid'); + $readData = $storage->read('id'); + $storage->close(); + $this->assertSame($sessionData, $readData, 'Written value can be read back correctly'); + } + + public function testReadConvertsStreamToString() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); + } + + $pdo = new MockPdo('pgsql'); + $pdo->prepareResult = $this->getMockBuilder('PDOStatement')->getMock(); + + $content = 'foobar'; + $stream = $this->createStream($content); + + $pdo->prepareResult->expects($this->once())->method('fetchAll') + ->will($this->returnValue(array(array($stream, 42, time())))); + + $storage = new PdoSessionHandler($pdo); + $result = $storage->read('foo'); + + $this->assertSame($content, $result); + } + + public function testReadLockedConvertsStreamToString() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); + } + + $pdo = new MockPdo('pgsql'); + $selectStmt = $this->getMockBuilder('PDOStatement')->getMock(); + $insertStmt = $this->getMockBuilder('PDOStatement')->getMock(); + + $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) { + return 0 === strpos($statement, 'INSERT') ? $insertStmt : $selectStmt; + }; + + $content = 'foobar'; + $stream = $this->createStream($content); + $exception = null; + + $selectStmt->expects($this->atLeast(2))->method('fetchAll') + ->will($this->returnCallback(function () use (&$exception, $stream) { + return $exception ? array(array($stream, 42, time())) : array(); + })); + + $insertStmt->expects($this->once())->method('execute') + ->will($this->returnCallback(function () use (&$exception) { + throw $exception = new \PDOException('', '23'); + })); + + $storage = new PdoSessionHandler($pdo); + $result = $storage->read('foo'); + + $this->assertSame($content, $result); + } + + public function testReadingRequiresExactlySameId() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->open('', 'sid'); + $storage->write('id', 'data'); + $storage->write('test', 'data'); + $storage->write('space ', 'data'); + $storage->close(); + + $storage->open('', 'sid'); + $readDataCaseSensitive = $storage->read('ID'); + $readDataNoCharFolding = $storage->read('tést'); + $readDataKeepSpace = $storage->read('space '); + $readDataExtraSpace = $storage->read('space '); + $storage->close(); + + $this->assertSame('', $readDataCaseSensitive, 'Retrieval by ID should be case-sensitive (collation setting)'); + $this->assertSame('', $readDataNoCharFolding, 'Retrieval by ID should not do character folding (collation setting)'); + $this->assertSame('data', $readDataKeepSpace, 'Retrieval by ID requires spaces as-is'); + $this->assertSame('', $readDataExtraSpace, 'Retrieval by ID requires spaces as-is'); + } + + /** + * Simulates session_regenerate_id(true) which will require an INSERT or UPDATE (replace). + */ + public function testWriteDifferentSessionIdThanRead() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->open('', 'sid'); + $storage->read('id'); + $storage->destroy('id'); + $storage->write('new_id', 'data_of_new_session_id'); + $storage->close(); + + $storage->open('', 'sid'); + $data = $storage->read('new_id'); + $storage->close(); + + $this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available'); + } + + public function testWrongUsageStillWorks() + { + // wrong method sequence that should no happen, but still works + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + $storage->write('id', 'data'); + $storage->write('other_id', 'other_data'); + $storage->destroy('inexistent'); + $storage->open('', 'sid'); + $data = $storage->read('id'); + $otherData = $storage->read('other_id'); + $storage->close(); + + $this->assertSame('data', $data); + $this->assertSame('other_data', $otherData); + } + + public function testSessionDestroy() + { + $pdo = $this->getMemorySqlitePdo(); + $storage = new PdoSessionHandler($pdo); + + $storage->open('', 'sid'); + $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn()); + + $storage->open('', 'sid'); + $storage->read('id'); + $storage->destroy('id'); + $storage->close(); + $this->assertEquals(0, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn()); + + $storage->open('', 'sid'); + $data = $storage->read('id'); + $storage->close(); + $this->assertSame('', $data, 'Destroyed session returns empty string'); + } + + public function testSessionGC() + { + $previousLifeTime = ini_set('session.gc_maxlifetime', 1000); + $pdo = $this->getMemorySqlitePdo(); + $storage = new PdoSessionHandler($pdo); + + $storage->open('', 'sid'); + $storage->read('id'); + $storage->write('id', 'data'); + $storage->close(); + + $storage->open('', 'sid'); + $storage->read('gc_id'); + ini_set('session.gc_maxlifetime', -1); // test that you can set lifetime of a session after it has been read + $storage->write('gc_id', 'data'); + $storage->close(); + $this->assertEquals(2, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'No session pruned because gc not called'); + + $storage->open('', 'sid'); + $data = $storage->read('gc_id'); + $storage->gc(-1); + $storage->close(); + + ini_set('session.gc_maxlifetime', $previousLifeTime); + + $this->assertSame('', $data, 'Session already considered garbage, so not returning data even if it is not pruned yet'); + $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'Expired session is pruned'); + } + + public function testGetConnection() + { + $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); + + $method = new \ReflectionMethod($storage, 'getConnection'); + $method->setAccessible(true); + + $this->assertInstanceOf('\PDO', $method->invoke($storage)); + } + + public function testGetConnectionConnectsIfNeeded() + { + $storage = new PdoSessionHandler('sqlite::memory:'); + + $method = new \ReflectionMethod($storage, 'getConnection'); + $method->setAccessible(true); + + $this->assertInstanceOf('\PDO', $method->invoke($storage)); + } + + private function createStream($content) + { + $stream = tmpfile(); + fwrite($stream, $content); + fseek($stream, 0); + + return $stream; + } +} + +class MockPdo extends \PDO +{ + public $prepareResult; + private $driverName; + private $errorMode; + + public function __construct($driverName = null, $errorMode = null) + { + $this->driverName = $driverName; + $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION; + } + + public function getAttribute($attribute) + { + if (\PDO::ATTR_ERRMODE === $attribute) { + return $this->errorMode; + } + + if (\PDO::ATTR_DRIVER_NAME === $attribute) { + return $this->driverName; + } + + return parent::getAttribute($attribute); + } + + public function prepare($statement, $driverOptions = array()) + { + return is_callable($this->prepareResult) + ? call_user_func($this->prepareResult, $statement, $driverOptions) + : $this->prepareResult; + } + + public function beginTransaction() + { + } + + public function rollBack() + { + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php new file mode 100644 index 00000000..5e41a474 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler; + +/** + * @author Adrien Brault + */ +class WriteCheckSessionHandlerTest extends TestCase +{ + public function test() + { + $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock); + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('close') + ->with() + ->will($this->returnValue(true)) + ; + + $this->assertTrue($writeCheckSessionHandler->close()); + } + + public function testWrite() + { + $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock); + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('write') + ->with('foo', 'bar') + ->will($this->returnValue(true)) + ; + + $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar')); + } + + public function testSkippedWrite() + { + $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock); + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('read') + ->with('foo') + ->will($this->returnValue('bar')) + ; + + $wrappedSessionHandlerMock + ->expects($this->never()) + ->method('write') + ; + + $this->assertEquals('bar', $writeCheckSessionHandler->read('foo')); + $this->assertTrue($writeCheckSessionHandler->write('foo', 'bar')); + } + + public function testNonSkippedWrite() + { + $wrappedSessionHandlerMock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock); + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('read') + ->with('foo') + ->will($this->returnValue('bar')) + ; + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('write') + ->with('foo', 'baZZZ') + ->will($this->returnValue(true)) + ; + + $this->assertEquals('bar', $writeCheckSessionHandler->read('foo')); + $this->assertTrue($writeCheckSessionHandler->write('foo', 'baZZZ')); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php new file mode 100644 index 00000000..159e6211 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Test class for MetadataBag. + * + * @group time-sensitive + */ +class MetadataBagTest extends TestCase +{ + /** + * @var MetadataBag + */ + protected $bag; + + /** + * @var array + */ + protected $array = array(); + + protected function setUp() + { + parent::setUp(); + $this->bag = new MetadataBag(); + $this->array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 0); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->array = array(); + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $sessionMetadata = array(); + + $bag1 = new MetadataBag(); + $bag1->initialize($sessionMetadata); + $this->assertGreaterThanOrEqual(time(), $bag1->getCreated()); + $this->assertEquals($bag1->getCreated(), $bag1->getLastUsed()); + + sleep(1); + $bag2 = new MetadataBag(); + $bag2->initialize($sessionMetadata); + $this->assertEquals($bag1->getCreated(), $bag2->getCreated()); + $this->assertEquals($bag1->getLastUsed(), $bag2->getLastUsed()); + $this->assertEquals($bag2->getCreated(), $bag2->getLastUsed()); + + sleep(1); + $bag3 = new MetadataBag(); + $bag3->initialize($sessionMetadata); + $this->assertEquals($bag1->getCreated(), $bag3->getCreated()); + $this->assertGreaterThan($bag2->getLastUsed(), $bag3->getLastUsed()); + $this->assertNotEquals($bag3->getCreated(), $bag3->getLastUsed()); + } + + public function testGetSetName() + { + $this->assertEquals('__metadata', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2_meta', $this->bag->getStorageKey()); + } + + public function testGetLifetime() + { + $bag = new MetadataBag(); + $array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 1000); + $bag->initialize($array); + $this->assertEquals(1000, $bag->getLifetime()); + } + + public function testGetCreated() + { + $this->assertEquals(1234567, $this->bag->getCreated()); + } + + public function testGetLastUsed() + { + $this->assertLessThanOrEqual(time(), $this->bag->getLastUsed()); + } + + public function testClear() + { + $this->bag->clear(); + + // the clear method has no side effects, we just want to ensure it doesn't trigger any exceptions + $this->addToAssertionCount(1); + } + + public function testSkipLastUsedUpdate() + { + $bag = new MetadataBag('', 30); + $timeStamp = time(); + + $created = $timeStamp - 15; + $sessionMetadata = array( + MetadataBag::CREATED => $created, + MetadataBag::UPDATED => $created, + MetadataBag::LIFETIME => 1000, + ); + $bag->initialize($sessionMetadata); + + $this->assertEquals($created, $sessionMetadata[MetadataBag::UPDATED]); + } + + public function testDoesNotSkipLastUsedUpdate() + { + $bag = new MetadataBag('', 30); + $timeStamp = time(); + + $created = $timeStamp - 45; + $sessionMetadata = array( + MetadataBag::CREATED => $created, + MetadataBag::UPDATED => $created, + MetadataBag::LIFETIME => 1000, + ); + $bag->initialize($sessionMetadata); + + $this->assertEquals($timeStamp, $sessionMetadata[MetadataBag::UPDATED]); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php new file mode 100644 index 00000000..82df5543 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; + +/** + * Test class for MockArraySessionStorage. + * + * @author Drak + */ +class MockArraySessionStorageTest extends TestCase +{ + /** + * @var MockArraySessionStorage + */ + private $storage; + + /** + * @var AttributeBag + */ + private $attributes; + + /** + * @var FlashBag + */ + private $flashes; + + private $data; + + protected function setUp() + { + $this->attributes = new AttributeBag(); + $this->flashes = new FlashBag(); + + $this->data = array( + $this->attributes->getStorageKey() => array('foo' => 'bar'), + $this->flashes->getStorageKey() => array('notice' => 'hello'), + ); + + $this->storage = new MockArraySessionStorage(); + $this->storage->registerBag($this->flashes); + $this->storage->registerBag($this->attributes); + $this->storage->setSessionData($this->data); + } + + protected function tearDown() + { + $this->data = null; + $this->flashes = null; + $this->attributes = null; + $this->storage = null; + } + + public function testStart() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $id = $this->storage->getId(); + $this->assertNotEquals('', $id); + $this->storage->start(); + $this->assertEquals($id, $this->storage->getId()); + } + + public function testRegenerate() + { + $this->storage->start(); + $id = $this->storage->getId(); + $this->storage->regenerate(); + $this->assertNotEquals($id, $this->storage->getId()); + $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all()); + $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll()); + + $id = $this->storage->getId(); + $this->storage->regenerate(true); + $this->assertNotEquals($id, $this->storage->getId()); + $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all()); + $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll()); + } + + public function testGetId() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $this->assertNotEquals('', $this->storage->getId()); + } + + public function testClearClearsBags() + { + $this->storage->clear(); + + $this->assertSame(array(), $this->storage->getBag('attributes')->all()); + $this->assertSame(array(), $this->storage->getBag('flashes')->peekAll()); + } + + public function testClearStartsSession() + { + $this->storage->clear(); + + $this->assertTrue($this->storage->isStarted()); + } + + public function testClearWithNoBagsStartsSession() + { + $storage = new MockArraySessionStorage(); + + $storage->clear(); + + $this->assertTrue($storage->isStarted()); + } + + /** + * @expectedException \RuntimeException + */ + public function testUnstartedSave() + { + $this->storage->save(); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.php new file mode 100644 index 00000000..53accd38 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.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\HttpFoundation\Tests\Session\Storage; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Test class for MockFileSessionStorage. + * + * @author Drak + */ +class MockFileSessionStorageTest extends TestCase +{ + /** + * @var string + */ + private $sessionDir; + + /** + * @var MockFileSessionStorage + */ + protected $storage; + + protected function setUp() + { + $this->sessionDir = sys_get_temp_dir().'/sf2test'; + $this->storage = $this->getStorage(); + } + + protected function tearDown() + { + $this->sessionDir = null; + $this->storage = null; + array_map('unlink', glob($this->sessionDir.'/*.session')); + if (is_dir($this->sessionDir)) { + rmdir($this->sessionDir); + } + } + + public function testStart() + { + $this->assertEquals('', $this->storage->getId()); + $this->assertTrue($this->storage->start()); + $id = $this->storage->getId(); + $this->assertNotEquals('', $this->storage->getId()); + $this->assertTrue($this->storage->start()); + $this->assertEquals($id, $this->storage->getId()); + } + + public function testRegenerate() + { + $this->storage->start(); + $this->storage->getBag('attributes')->set('regenerate', 1234); + $this->storage->regenerate(); + $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate')); + $this->storage->regenerate(true); + $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate')); + } + + public function testGetId() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $this->assertNotEquals('', $this->storage->getId()); + } + + public function testSave() + { + $this->storage->start(); + $id = $this->storage->getId(); + $this->assertNotEquals('108', $this->storage->getBag('attributes')->get('new')); + $this->assertFalse($this->storage->getBag('flashes')->has('newkey')); + $this->storage->getBag('attributes')->set('new', '108'); + $this->storage->getBag('flashes')->set('newkey', 'test'); + $this->storage->save(); + + $storage = $this->getStorage(); + $storage->setId($id); + $storage->start(); + $this->assertEquals('108', $storage->getBag('attributes')->get('new')); + $this->assertTrue($storage->getBag('flashes')->has('newkey')); + $this->assertEquals(array('test'), $storage->getBag('flashes')->peek('newkey')); + } + + public function testMultipleInstances() + { + $storage1 = $this->getStorage(); + $storage1->start(); + $storage1->getBag('attributes')->set('foo', 'bar'); + $storage1->save(); + + $storage2 = $this->getStorage(); + $storage2->setId($storage1->getId()); + $storage2->start(); + $this->assertEquals('bar', $storage2->getBag('attributes')->get('foo'), 'values persist between instances'); + } + + /** + * @expectedException \RuntimeException + */ + public function testSaveWithoutStart() + { + $storage1 = $this->getStorage(); + $storage1->save(); + } + + private function getStorage() + { + $storage = new MockFileSessionStorage($this->sessionDir); + $storage->registerBag(new FlashBag()); + $storage->registerBag(new AttributeBag()); + + return $storage; + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php new file mode 100644 index 00000000..818c63a9 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * Test class for NativeSessionStorage. + * + * @author Drak + * + * These tests require separate processes. + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class NativeSessionStorageTest extends TestCase +{ + private $savePath; + + protected function setUp() + { + $this->iniSet('session.save_handler', 'files'); + $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test'); + if (!is_dir($this->savePath)) { + mkdir($this->savePath); + } + } + + protected function tearDown() + { + session_write_close(); + array_map('unlink', glob($this->savePath.'/*')); + if (is_dir($this->savePath)) { + rmdir($this->savePath); + } + + $this->savePath = null; + } + + /** + * @param array $options + * + * @return NativeSessionStorage + */ + protected function getStorage(array $options = array()) + { + $storage = new NativeSessionStorage($options); + $storage->registerBag(new AttributeBag()); + + return $storage; + } + + public function testBag() + { + $storage = $this->getStorage(); + $bag = new FlashBag(); + $storage->registerBag($bag); + $this->assertSame($bag, $storage->getBag($bag->getName())); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRegisterBagException() + { + $storage = $this->getStorage(); + $storage->getBag('non_existing'); + } + + /** + * @expectedException \LogicException + */ + public function testRegisterBagForAStartedSessionThrowsException() + { + $storage = $this->getStorage(); + $storage->start(); + $storage->registerBag(new AttributeBag()); + } + + public function testGetId() + { + $storage = $this->getStorage(); + $this->assertSame('', $storage->getId(), 'Empty ID before starting session'); + + $storage->start(); + $id = $storage->getId(); + $this->assertInternalType('string', $id); + $this->assertNotSame('', $id); + + $storage->save(); + $this->assertSame($id, $storage->getId(), 'ID stays after saving session'); + } + + public function testRegenerate() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('lucky', 7); + $storage->regenerate(); + $this->assertNotEquals($id, $storage->getId()); + $this->assertEquals(7, $storage->getBag('attributes')->get('lucky')); + } + + public function testRegenerateDestroy() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('legs', 11); + $storage->regenerate(true); + $this->assertNotEquals($id, $storage->getId()); + $this->assertEquals(11, $storage->getBag('attributes')->get('legs')); + } + + public function testSessionGlobalIsUpToDateAfterIdRegeneration() + { + $storage = $this->getStorage(); + $storage->start(); + $storage->getBag('attributes')->set('lucky', 7); + $storage->regenerate(); + $storage->getBag('attributes')->set('lucky', 42); + + $this->assertEquals(42, $_SESSION['_sf2_attributes']['lucky']); + } + + public function testRegenerationFailureDoesNotFlagStorageAsStarted() + { + $storage = $this->getStorage(); + $this->assertFalse($storage->regenerate()); + $this->assertFalse($storage->isStarted()); + } + + public function testDefaultSessionCacheLimiter() + { + $this->iniSet('session.cache_limiter', 'nocache'); + + $storage = new NativeSessionStorage(); + $this->assertEquals('', ini_get('session.cache_limiter')); + } + + public function testExplicitSessionCacheLimiter() + { + $this->iniSet('session.cache_limiter', 'nocache'); + + $storage = new NativeSessionStorage(array('cache_limiter' => 'public')); + $this->assertEquals('public', ini_get('session.cache_limiter')); + } + + public function testCookieOptions() + { + $options = array( + 'cookie_lifetime' => 123456, + 'cookie_path' => '/my/cookie/path', + 'cookie_domain' => 'symfony.example.com', + 'cookie_secure' => true, + 'cookie_httponly' => false, + ); + + $this->getStorage($options); + $temp = session_get_cookie_params(); + $gco = array(); + + foreach ($temp as $key => $value) { + $gco['cookie_'.$key] = $value; + } + + $this->assertEquals($options, $gco); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetSaveHandlerException() + { + $storage = $this->getStorage(); + $storage->setSaveHandler(new \stdClass()); + } + + public function testSetSaveHandler() + { + $this->iniSet('session.save_handler', 'files'); + $storage = $this->getStorage(); + $storage->setSaveHandler(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(null); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new SessionHandlerProxy(new NativeSessionHandler())); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new NativeSessionHandler()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new SessionHandlerProxy(new NullSessionHandler())); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new NullSessionHandler()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + } + + /** + * @expectedException \RuntimeException + */ + public function testStarted() + { + $storage = $this->getStorage(); + + $this->assertFalse($storage->getSaveHandler()->isActive()); + $this->assertFalse($storage->isStarted()); + + session_start(); + $this->assertTrue(isset($_SESSION)); + $this->assertTrue($storage->getSaveHandler()->isActive()); + + // PHP session might have started, but the storage driver has not, so false is correct here + $this->assertFalse($storage->isStarted()); + + $key = $storage->getMetadataBag()->getStorageKey(); + $this->assertFalse(isset($_SESSION[$key])); + $storage->start(); + } + + public function testRestart() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('lucky', 7); + $storage->save(); + $storage->start(); + $this->assertSame($id, $storage->getId(), 'Same session ID after restarting'); + $this->assertSame(7, $storage->getBag('attributes')->get('lucky'), 'Data still available'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php new file mode 100644 index 00000000..b8b98386 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Test class for PhpSessionStorage. + * + * @author Drak + * + * These tests require separate processes. + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class PhpBridgeSessionStorageTest extends TestCase +{ + private $savePath; + + protected function setUp() + { + $this->iniSet('session.save_handler', 'files'); + $this->iniSet('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test'); + if (!is_dir($this->savePath)) { + mkdir($this->savePath); + } + } + + protected function tearDown() + { + session_write_close(); + array_map('unlink', glob($this->savePath.'/*')); + if (is_dir($this->savePath)) { + rmdir($this->savePath); + } + + $this->savePath = null; + } + + /** + * @return PhpBridgeSessionStorage + */ + protected function getStorage() + { + $storage = new PhpBridgeSessionStorage(); + $storage->registerBag(new AttributeBag()); + + return $storage; + } + + public function testPhpSession() + { + $storage = $this->getStorage(); + + $this->assertFalse($storage->getSaveHandler()->isActive()); + $this->assertFalse($storage->isStarted()); + + session_start(); + $this->assertTrue(isset($_SESSION)); + // in PHP 5.4 we can reliably detect a session started + $this->assertTrue($storage->getSaveHandler()->isActive()); + // PHP session might have started, but the storage driver has not, so false is correct here + $this->assertFalse($storage->isStarted()); + + $key = $storage->getMetadataBag()->getStorageKey(); + $this->assertFalse(isset($_SESSION[$key])); + $storage->start(); + $this->assertTrue(isset($_SESSION[$key])); + } + + public function testClear() + { + $storage = $this->getStorage(); + session_start(); + $_SESSION['drak'] = 'loves symfony'; + $storage->getBag('attributes')->set('symfony', 'greatness'); + $key = $storage->getBag('attributes')->getStorageKey(); + $this->assertEquals($_SESSION[$key], array('symfony' => 'greatness')); + $this->assertEquals($_SESSION['drak'], 'loves symfony'); + $storage->clear(); + $this->assertEquals($_SESSION[$key], array()); + $this->assertEquals($_SESSION['drak'], 'loves symfony'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php new file mode 100644 index 00000000..ef1da130 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +// Note until PHPUnit_Mock_Objects 1.2 is released you cannot mock abstracts due to +// https://github.com/sebastianbergmann/phpunit-mock-objects/issues/73 +class ConcreteProxy extends AbstractProxy +{ +} + +class ConcreteSessionHandlerInterfaceProxy extends AbstractProxy implements \SessionHandlerInterface +{ + public function open($savePath, $sessionName) + { + } + + public function close() + { + } + + public function read($id) + { + } + + public function write($id, $data) + { + } + + public function destroy($id) + { + } + + public function gc($maxlifetime) + { + } +} + +/** + * Test class for AbstractProxy. + * + * @author Drak + */ +class AbstractProxyTest extends TestCase +{ + /** + * @var AbstractProxy + */ + protected $proxy; + + protected function setUp() + { + $this->proxy = new ConcreteProxy(); + } + + protected function tearDown() + { + $this->proxy = null; + } + + public function testGetSaveHandlerName() + { + $this->assertNull($this->proxy->getSaveHandlerName()); + } + + public function testIsSessionHandlerInterface() + { + $this->assertFalse($this->proxy->isSessionHandlerInterface()); + $sh = new ConcreteSessionHandlerInterfaceProxy(); + $this->assertTrue($sh->isSessionHandlerInterface()); + } + + public function testIsWrapper() + { + $this->assertFalse($this->proxy->isWrapper()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testIsActive() + { + $this->assertFalse($this->proxy->isActive()); + session_start(); + $this->assertTrue($this->proxy->isActive()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testName() + { + $this->assertEquals(session_name(), $this->proxy->getName()); + $this->proxy->setName('foo'); + $this->assertEquals('foo', $this->proxy->getName()); + $this->assertEquals(session_name(), $this->proxy->getName()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @expectedException \LogicException + */ + public function testNameException() + { + session_start(); + $this->proxy->setName('foo'); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testId() + { + $this->assertEquals(session_id(), $this->proxy->getId()); + $this->proxy->setId('foo'); + $this->assertEquals('foo', $this->proxy->getId()); + $this->assertEquals(session_id(), $this->proxy->getId()); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @expectedException \LogicException + */ + public function testIdException() + { + session_start(); + $this->proxy->setId('foo'); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.php new file mode 100644 index 00000000..8ec30534 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.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\HttpFoundation\Tests\Session\Storage\Proxy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy; + +/** + * Test class for NativeProxy. + * + * @author Drak + */ +class NativeProxyTest extends TestCase +{ + public function testIsWrapper() + { + $proxy = new NativeProxy(); + $this->assertFalse($proxy->isWrapper()); + } + + public function testGetSaveHandlerName() + { + $name = ini_get('session.save_handler'); + $proxy = new NativeProxy(); + $this->assertEquals($name, $proxy->getSaveHandlerName()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php new file mode 100644 index 00000000..68282535 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * Tests for SessionHandlerProxy class. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class SessionHandlerProxyTest extends TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_Matcher + */ + private $mock; + + /** + * @var SessionHandlerProxy + */ + private $proxy; + + protected function setUp() + { + $this->mock = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $this->proxy = new SessionHandlerProxy($this->mock); + } + + protected function tearDown() + { + $this->mock = null; + $this->proxy = null; + } + + public function testOpenTrue() + { + $this->mock->expects($this->once()) + ->method('open') + ->will($this->returnValue(true)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->open('name', 'id'); + $this->assertFalse($this->proxy->isActive()); + } + + public function testOpenFalse() + { + $this->mock->expects($this->once()) + ->method('open') + ->will($this->returnValue(false)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->open('name', 'id'); + $this->assertFalse($this->proxy->isActive()); + } + + public function testClose() + { + $this->mock->expects($this->once()) + ->method('close') + ->will($this->returnValue(true)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->close(); + $this->assertFalse($this->proxy->isActive()); + } + + public function testCloseFalse() + { + $this->mock->expects($this->once()) + ->method('close') + ->will($this->returnValue(false)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->close(); + $this->assertFalse($this->proxy->isActive()); + } + + public function testRead() + { + $this->mock->expects($this->once()) + ->method('read'); + + $this->proxy->read('id'); + } + + public function testWrite() + { + $this->mock->expects($this->once()) + ->method('write'); + + $this->proxy->write('id', 'data'); + } + + public function testDestroy() + { + $this->mock->expects($this->once()) + ->method('destroy'); + + $this->proxy->destroy('id'); + } + + public function testGc() + { + $this->mock->expects($this->once()) + ->method('gc'); + + $this->proxy->gc(86400); + } +} diff --git a/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php b/vendor/symfony/http-foundation/Tests/StreamedResponseTest.php new file mode 100644 index 00000000..1e35eb88 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/StreamedResponseTest.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\HttpFoundation\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\StreamedResponse; + +class StreamedResponseTest extends TestCase +{ + public function testConstructor() + { + $response = new StreamedResponse(function () { echo 'foo'; }, 404, array('Content-Type' => 'text/plain')); + + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals('text/plain', $response->headers->get('Content-Type')); + } + + public function testPrepareWith11Protocol() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $request = Request::create('/'); + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1'); + + $response->prepare($request); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + $this->assertNotEquals('chunked', $response->headers->get('Transfer-Encoding'), 'Apache assumes responses with a Transfer-Encoding header set to chunked to already be encoded.'); + } + + public function testPrepareWith10Protocol() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $request = Request::create('/'); + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0'); + + $response->prepare($request); + + $this->assertEquals('1.0', $response->getProtocolVersion()); + $this->assertNull($response->headers->get('Transfer-Encoding')); + } + + public function testPrepareWithHeadRequest() + { + $response = new StreamedResponse(function () { echo 'foo'; }, 200, array('Content-Length' => '123')); + $request = Request::create('/', 'HEAD'); + + $response->prepare($request); + + $this->assertSame('123', $response->headers->get('Content-Length')); + } + + public function testPrepareWithCacheHeaders() + { + $response = new StreamedResponse(function () { echo 'foo'; }, 200, array('Cache-Control' => 'max-age=600, public')); + $request = Request::create('/', 'GET'); + + $response->prepare($request); + $this->assertEquals('max-age=600, public', $response->headers->get('Cache-Control')); + } + + public function testSendContent() + { + $called = 0; + + $response = new StreamedResponse(function () use (&$called) { ++$called; }); + + $response->sendContent(); + $this->assertEquals(1, $called); + + $response->sendContent(); + $this->assertEquals(1, $called); + } + + /** + * @expectedException \LogicException + */ + public function testSendContentWithNonCallable() + { + $response = new StreamedResponse(null); + $response->sendContent(); + } + + /** + * @expectedException \LogicException + */ + public function testSetContent() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $response->setContent('foo'); + } + + public function testGetContent() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $this->assertFalse($response->getContent()); + } + + public function testCreate() + { + $response = StreamedResponse::create(function () {}, 204); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response); + $this->assertEquals(204, $response->getStatusCode()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng b/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng new file mode 100644 index 00000000..73708ca6 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/schema/http-status-codes.rng @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng b/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng new file mode 100644 index 00000000..b9c3ca9d --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/schema/iana-registry.rng @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + uri + + + + rfc + + + (rfc|bcp|std)\d+ + + + + + rfc-errata + + + + draft + + + (draft|RFC)(-[a-zA-Z0-9]+)+ + + + + + registry + + + + person + + + + text + + + note + + + + unicode + + + ucd\d+\.\d+\.\d+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (\d+|0x[\da-fA-F]+)(\s*-\s*(\d+|0x[\da-fA-F]+))? + + + + + + + + + + + + + 0x[0-9]{8} + + + + + + [0-1]+ + + + + + + + + + + + + + + + + + + + + + + legacy + mib + template + json + + + + + + + + + + diff --git a/vendor/symfony/http-foundation/composer.json b/vendor/symfony/http-foundation/composer.json new file mode 100644 index 00000000..dfa25f79 --- /dev/null +++ b/vendor/symfony/http-foundation/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/http-foundation", + "type": "library", + "description": "Symfony HttpFoundation Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/http-foundation/phpunit.xml.dist b/vendor/symfony/http-foundation/phpunit.xml.dist new file mode 100644 index 00000000..c1d61f8b --- /dev/null +++ b/vendor/symfony/http-foundation/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/http-kernel/.gitignore b/vendor/symfony/http-kernel/.gitignore new file mode 100644 index 00000000..94a6a252 --- /dev/null +++ b/vendor/symfony/http-kernel/.gitignore @@ -0,0 +1,5 @@ +vendor/ +composer.lock +phpunit.xml +Tests/Fixtures/cache/ +Tests/Fixtures/logs/ diff --git a/vendor/symfony/http-kernel/Bundle/Bundle.php b/vendor/symfony/http-kernel/Bundle/Bundle.php new file mode 100644 index 00000000..0b0ea088 --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/Bundle.php @@ -0,0 +1,231 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerAwareTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\Console\Application; +use Symfony\Component\Finder\Finder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * An implementation of BundleInterface that adds a few conventions + * for DependencyInjection extensions and Console commands. + * + * @author Fabien Potencier + */ +abstract class Bundle implements BundleInterface +{ + use ContainerAwareTrait; + + protected $name; + protected $extension; + protected $path; + private $namespace; + + /** + * Boots the Bundle. + */ + public function boot() + { + } + + /** + * Shutdowns the Bundle. + */ + public function shutdown() + { + } + + /** + * Builds the bundle. + * + * It is only ever called once when the cache is empty. + * + * This method can be overridden to register compilation passes, + * other extensions, ... + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + public function build(ContainerBuilder $container) + { + } + + /** + * Returns the bundle's container extension. + * + * @return ExtensionInterface|null The container extension + * + * @throws \LogicException + */ + public function getContainerExtension() + { + if (null === $this->extension) { + $extension = $this->createContainerExtension(); + + if (null !== $extension) { + if (!$extension instanceof ExtensionInterface) { + throw new \LogicException(sprintf('Extension %s must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', get_class($extension))); + } + + // check naming convention + $basename = preg_replace('/Bundle$/', '', $this->getName()); + $expectedAlias = Container::underscore($basename); + + if ($expectedAlias != $extension->getAlias()) { + throw new \LogicException(sprintf( + 'Users will expect the alias of the default extension of a bundle to be the underscored version of the bundle name ("%s"). You can override "Bundle::getContainerExtension()" if you want to use "%s" or another alias.', + $expectedAlias, $extension->getAlias() + )); + } + + $this->extension = $extension; + } else { + $this->extension = false; + } + } + + if ($this->extension) { + return $this->extension; + } + } + + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + */ + public function getNamespace() + { + if (null === $this->namespace) { + $this->parseClassName(); + } + + return $this->namespace; + } + + /** + * Gets the Bundle directory path. + * + * @return string The Bundle absolute path + */ + public function getPath() + { + if (null === $this->path) { + $reflected = new \ReflectionObject($this); + $this->path = dirname($reflected->getFileName()); + } + + return $this->path; + } + + /** + * Returns the bundle parent name. + * + * @return string|null The Bundle parent name it overrides or null if no parent + */ + public function getParent() + { + } + + /** + * Returns the bundle name (the class short name). + * + * @return string The Bundle name + */ + final public function getName() + { + if (null === $this->name) { + $this->parseClassName(); + } + + return $this->name; + } + + /** + * Finds and registers Commands. + * + * Override this method if your bundle commands do not follow the conventions: + * + * * Commands are in the 'Command' sub-directory + * * Commands extend Symfony\Component\Console\Command\Command + * + * @param Application $application An Application instance + */ + public function registerCommands(Application $application) + { + if (!is_dir($dir = $this->getPath().'/Command')) { + return; + } + + if (!class_exists('Symfony\Component\Finder\Finder')) { + throw new \RuntimeException('You need the symfony/finder component to register bundle commands.'); + } + + $finder = new Finder(); + $finder->files()->name('*Command.php')->in($dir); + + $prefix = $this->getNamespace().'\\Command'; + foreach ($finder as $file) { + $ns = $prefix; + if ($relativePath = $file->getRelativePath()) { + $ns .= '\\'.str_replace('/', '\\', $relativePath); + } + $class = $ns.'\\'.$file->getBasename('.php'); + if ($this->container) { + $commandIds = $this->container->hasParameter('console.command.ids') ? $this->container->getParameter('console.command.ids') : array(); + $alias = 'console.command.'.strtolower(str_replace('\\', '_', $class)); + if (isset($commandIds[$alias]) || $this->container->has($alias)) { + continue; + } + } + $r = new \ReflectionClass($class); + if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) { + $application->add($r->newInstance()); + } + } + } + + /** + * Returns the bundle's container extension class. + * + * @return string + */ + protected function getContainerExtensionClass() + { + $basename = preg_replace('/Bundle$/', '', $this->getName()); + + return $this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension'; + } + + /** + * Creates the bundle's container extension. + * + * @return ExtensionInterface|null + */ + protected function createContainerExtension() + { + if (class_exists($class = $this->getContainerExtensionClass())) { + return new $class(); + } + } + + private function parseClassName() + { + $pos = strrpos(static::class, '\\'); + $this->namespace = false === $pos ? '' : substr(static::class, 0, $pos); + if (null === $this->name) { + $this->name = false === $pos ? static::class : substr(static::class, $pos + 1); + } + } +} diff --git a/vendor/symfony/http-kernel/Bundle/BundleInterface.php b/vendor/symfony/http-kernel/Bundle/BundleInterface.php new file mode 100644 index 00000000..25eea1d7 --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/BundleInterface.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\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * BundleInterface. + * + * @author Fabien Potencier + */ +interface BundleInterface extends ContainerAwareInterface +{ + /** + * Boots the Bundle. + */ + public function boot(); + + /** + * Shutdowns the Bundle. + */ + public function shutdown(); + + /** + * Builds the bundle. + * + * It is only ever called once when the cache is empty. + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + public function build(ContainerBuilder $container); + + /** + * Returns the container extension that should be implicitly loaded. + * + * @return ExtensionInterface|null The default extension or null if there is none + */ + public function getContainerExtension(); + + /** + * Returns the bundle name that this bundle overrides. + * + * Despite its name, this method does not imply any parent/child relationship + * between the bundles, just a way to extend and override an existing + * bundle. + * + * @return string The Bundle name it overrides or null if no parent + */ + public function getParent(); + + /** + * Returns the bundle name (the class short name). + * + * @return string The Bundle name + */ + public function getName(); + + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + */ + public function getNamespace(); + + /** + * Gets the Bundle directory path. + * + * The path should always be returned as a Unix path (with /). + * + * @return string The Bundle absolute path + */ + public function getPath(); +} diff --git a/vendor/symfony/http-kernel/CHANGELOG.md b/vendor/symfony/http-kernel/CHANGELOG.md new file mode 100644 index 00000000..061f61d1 --- /dev/null +++ b/vendor/symfony/http-kernel/CHANGELOG.md @@ -0,0 +1,134 @@ +CHANGELOG +========= + +3.3.0 +----- + + * added `kernel.project_dir` and `Kernel::getProjectDir()` + * deprecated `kernel.root_dir` and `Kernel::getRootDir()` + * deprecated `Kernel::getEnvParameters()` + * deprecated the special `SYMFONY__` environment variables + * added the possibility to change the query string parameter used by `UriSigner` + * deprecated `LazyLoadingFragmentHandler::addRendererService()` + * deprecated `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` + * deprecated `Psr6CacheClearer::addPool()` + +3.2.0 +----- + + * deprecated `DataCollector::varToString()`, use `cloneVar()` instead + * changed surrogate capability name in `AbstractSurrogate::addSurrogateCapability` to 'symfony' + * Added `ControllerArgumentValueResolverPass` + +3.1.0 +----- + * deprecated passing objects as URI attributes to the ESI and SSI renderers + * deprecated `ControllerResolver::getArguments()` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` as argument to `HttpKernel` + * added `Symfony\Component\HttpKernel\Controller\ArgumentResolver` + * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getMethod()` + * added `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector::getRedirect()` + * added the `kernel.controller_arguments` event, triggered after controller arguments have been resolved + +3.0.0 +----- + + * removed `Symfony\Component\HttpKernel\Kernel::init()` + * removed `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle()` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle()` + * removed `Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher::setProfiler()` + * removed `Symfony\Component\HttpKernel\EventListener\FragmentListener::getLocalIpAddresses()` + * removed `Symfony\Component\HttpKernel\EventListener\LocaleListener::setRequest()` + * removed `Symfony\Component\HttpKernel\EventListener\RouterListener::setRequest()` + * removed `Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelRequest()` + * removed `Symfony\Component\HttpKernel\Fragment\FragmentHandler::setRequest()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::hasSurrogateEsiCapability()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::addSurrogateEsiCapability()` + * removed `Symfony\Component\HttpKernel\HttpCache\Esi::needsEsiParsing()` + * removed `Symfony\Component\HttpKernel\HttpCache\HttpCache::getEsi()` + * removed `Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel` + * removed `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass` + * removed `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` + * removed `Symfony\Component\HttpKernel\EventListener\EsiListener` + * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategy` + * removed `Symfony\Component\HttpKernel\HttpCache\EsiResponseCacheStrategyInterface` + * removed `Symfony\Component\HttpKernel\Log\LoggerInterface` + * removed `Symfony\Component\HttpKernel\Log\NullLogger` + * removed `Symfony\Component\HttpKernel\Profiler::import()` + * removed `Symfony\Component\HttpKernel\Profiler::export()` + +2.8.0 +----- + + * deprecated `Profiler::import` and `Profiler::export` + +2.7.0 +----- + + * added the HTTP status code to profiles + +2.6.0 +----- + + * deprecated `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener`, use `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` instead + * deprecated unused method `Symfony\Component\HttpKernel\Kernel::isClassInActiveBundle` and `Symfony\Component\HttpKernel\KernelInterface::isClassInActiveBundle` + +2.5.0 +----- + + * deprecated `Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass`, use `Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass` instead + +2.4.0 +----- + + * added event listeners for the session + * added the KernelEvents::FINISH_REQUEST event + +2.3.0 +----- + + * [BC BREAK] renamed `Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener` to `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` and changed its constructor + * deprecated `Symfony\Component\HttpKernel\Debug\ErrorHandler`, `Symfony\Component\HttpKernel\Debug\ExceptionHandler`, + `Symfony\Component\HttpKernel\Exception\FatalErrorException` and `Symfony\Component\HttpKernel\Exception\FlattenException` + * deprecated `Symfony\Component\HttpKernel\Kernel::init()` + * added the possibility to specify an id an extra attributes to hinclude tags + * added the collect of data if a controller is a Closure in the Request collector + * pass exceptions from the ExceptionListener to the logger using the logging context to allow for more + detailed messages + +2.2.0 +----- + + * [BC BREAK] the path info for sub-request is now always _fragment (or whatever you configured instead of the default) + * added Symfony\Component\HttpKernel\EventListener\FragmentListener + * added Symfony\Component\HttpKernel\UriSigner + * added Symfony\Component\HttpKernel\FragmentRenderer and rendering strategies (in Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface) + * added Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel + * added ControllerReference to create reference of Controllers (used in the FragmentRenderer class) + * [BC BREAK] renamed TimeDataCollector::getTotalTime() to + TimeDataCollector::getDuration() + * updated the MemoryDataCollector to include the memory used in the + kernel.terminate event listeners + * moved the Stopwatch classes to a new component + * added TraceableControllerResolver + * added TraceableEventDispatcher (removed ContainerAwareTraceableEventDispatcher) + * added support for WinCache opcode cache in ConfigDataCollector + +2.1.0 +----- + + * [BC BREAK] the charset is now configured via the Kernel::getCharset() method + * [BC BREAK] the current locale for the user is not stored anymore in the session + * added the HTTP method to the profiler storage + * updated all listeners to implement EventSubscriberInterface + * added TimeDataCollector + * added ContainerAwareTraceableEventDispatcher + * moved TraceableEventDispatcherInterface to the EventDispatcher component + * added RouterListener, LocaleListener, and StreamedResponseListener + * added CacheClearerInterface (and ChainCacheClearer) + * added a kernel.terminate event (via TerminableInterface and PostResponseEvent) + * added a Stopwatch class + * added WarmableInterface + * improved extensibility between bundles + * added profiler storages for Memcache(d), File-based, MongoDB, Redis + * moved Filesystem class to its own component diff --git a/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php new file mode 100644 index 00000000..675c5842 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.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\HttpKernel\CacheClearer; + +/** + * CacheClearerInterface. + * + * @author Dustin Dobervich + */ +interface CacheClearerInterface +{ + /** + * Clears any caches necessary. + * + * @param string $cacheDir The cache directory + */ + public function clear($cacheDir); +} diff --git a/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php new file mode 100644 index 00000000..c749c7c0 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * ChainCacheClearer. + * + * @author Dustin Dobervich + */ +class ChainCacheClearer implements CacheClearerInterface +{ + /** + * @var array + */ + protected $clearers; + + /** + * Constructs a new instance of ChainCacheClearer. + * + * @param array $clearers The initial clearers + */ + public function __construct(array $clearers = array()) + { + $this->clearers = $clearers; + } + + /** + * {@inheritdoc} + */ + public function clear($cacheDir) + { + foreach ($this->clearers as $clearer) { + $clearer->clear($cacheDir); + } + } + + /** + * Adds a cache clearer to the aggregate. + * + * @param CacheClearerInterface $clearer + */ + public function add(CacheClearerInterface $clearer) + { + $this->clearers[] = $clearer; + } +} diff --git a/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php b/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php new file mode 100644 index 00000000..2336b18a --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Nicolas Grekas + */ +class Psr6CacheClearer implements CacheClearerInterface +{ + private $pools = array(); + + public function __construct(array $pools = array()) + { + $this->pools = $pools; + } + + public function addPool(CacheItemPoolInterface $pool) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead.', __METHOD__), E_USER_DEPRECATED); + + $this->pools[] = $pool; + } + + public function hasPool($name) + { + return isset($this->pools[$name]); + } + + public function clearPool($name) + { + if (!isset($this->pools[$name])) { + throw new \InvalidArgumentException(sprintf('Cache pool not found: %s.', $name)); + } + + return $this->pools[$name]->clear(); + } + + /** + * {@inheritdoc} + */ + public function clear($cacheDir) + { + foreach ($this->pools as $pool) { + $pool->clear(); + } + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php new file mode 100644 index 00000000..dba35a63 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.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\HttpKernel\CacheWarmer; + +/** + * Abstract cache warmer that knows how to write a file to the cache. + * + * @author Fabien Potencier + */ +abstract class CacheWarmer implements CacheWarmerInterface +{ + protected function writeCacheFile($file, $content) + { + $tmpFile = @tempnam(dirname($file), basename($file)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { + @chmod($file, 0666 & ~umask()); + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php new file mode 100644 index 00000000..e5f4e4fa --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Aggregates several cache warmers into a single one. + * + * @author Fabien Potencier + */ +class CacheWarmerAggregate implements CacheWarmerInterface +{ + protected $warmers = array(); + protected $optionalsEnabled = false; + + public function __construct(array $warmers = array()) + { + foreach ($warmers as $warmer) { + $this->add($warmer); + } + } + + public function enableOptionalWarmers() + { + $this->optionalsEnabled = true; + } + + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir) + { + foreach ($this->warmers as $warmer) { + if (!$this->optionalsEnabled && $warmer->isOptional()) { + continue; + } + + $warmer->warmUp($cacheDir); + } + } + + /** + * Checks whether this warmer is optional or not. + * + * @return bool always false + */ + public function isOptional() + { + return false; + } + + public function setWarmers(array $warmers) + { + $this->warmers = array(); + foreach ($warmers as $warmer) { + $this->add($warmer); + } + } + + public function add(CacheWarmerInterface $warmer) + { + $this->warmers[] = $warmer; + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php new file mode 100644 index 00000000..8fece5e9 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.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\HttpKernel\CacheWarmer; + +/** + * Interface for classes able to warm up the cache. + * + * @author Fabien Potencier + */ +interface CacheWarmerInterface extends WarmableInterface +{ + /** + * Checks whether this warmer is optional or not. + * + * Optional warmers can be ignored on certain conditions. + * + * A warmer should return true if the cache can be + * generated incrementally and on-demand. + * + * @return bool true if the warmer is optional, false otherwise + */ + public function isOptional(); +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php new file mode 100644 index 00000000..25d8ee8f --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.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\HttpKernel\CacheWarmer; + +/** + * Interface for classes that support warming their cache. + * + * @author Fabien Potencier + */ +interface WarmableInterface +{ + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir); +} diff --git a/vendor/symfony/http-kernel/Client.php b/vendor/symfony/http-kernel/Client.php new file mode 100644 index 00000000..94f70cd6 --- /dev/null +++ b/vendor/symfony/http-kernel/Client.php @@ -0,0 +1,206 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\BrowserKit\Client as BaseClient; +use Symfony\Component\BrowserKit\Request as DomRequest; +use Symfony\Component\BrowserKit\Response as DomResponse; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Client simulates a browser and makes requests to a Kernel object. + * + * @author Fabien Potencier + * + * @method Request|null getRequest() A Request instance + * @method Response|null getResponse() A Response instance + */ +class Client extends BaseClient +{ + protected $kernel; + + /** + * Constructor. + * + * @param HttpKernelInterface $kernel An HttpKernel instance + * @param array $server The server parameters (equivalent of $_SERVER) + * @param History $history A History instance to store the browser history + * @param CookieJar $cookieJar A CookieJar instance to store the cookies + */ + public function __construct(HttpKernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + // These class properties must be set before calling the parent constructor, as it may depend on it. + $this->kernel = $kernel; + $this->followRedirects = false; + + parent::__construct($server, $history, $cookieJar); + } + + /** + * Makes a request. + * + * @param Request $request A Request instance + * + * @return Response A Response instance + */ + protected function doRequest($request) + { + $response = $this->kernel->handle($request); + + if ($this->kernel instanceof TerminableInterface) { + $this->kernel->terminate($request, $response); + } + + return $response; + } + + /** + * Returns the script to execute when the request must be insulated. + * + * @param Request $request A Request instance + * + * @return string + */ + protected function getScript($request) + { + $kernel = str_replace("'", "\\'", serialize($this->kernel)); + $request = str_replace("'", "\\'", serialize($request)); + $errorReporting = error_reporting(); + + $requires = ''; + foreach (get_declared_classes() as $class) { + if (0 === strpos($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $file = dirname(dirname($r->getFileName())).'/autoload.php'; + if (file_exists($file)) { + $requires .= "require_once '".str_replace("'", "\\'", $file)."';\n"; + } + } + } + + if (!$requires) { + throw new \RuntimeException('Composer autoloader not found.'); + } + + $code = <<getHandleScript(); + } + + protected function getHandleScript() + { + return <<<'EOF' +$response = $kernel->handle($request); + +if ($kernel instanceof Symfony\Component\HttpKernel\TerminableInterface) { + $kernel->terminate($request, $response); +} + +echo serialize($response); +EOF; + } + + /** + * Converts the BrowserKit request to a HttpKernel request. + * + * @param DomRequest $request A DomRequest instance + * + * @return Request A Request instance + */ + protected function filterRequest(DomRequest $request) + { + $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $request->getServer(), $request->getContent()); + + foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) { + $httpRequest->files->set($key, $value); + } + + return $httpRequest; + } + + /** + * Filters an array of files. + * + * This method created test instances of UploadedFile so that the move() + * method can be called on those instances. + * + * If the size of a file is greater than the allowed size (from php.ini) then + * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. + * + * @see UploadedFile + * + * @param array $files An array of files + * + * @return array An array with all uploaded files marked as already moved + */ + protected function filterFiles(array $files) + { + $filtered = array(); + foreach ($files as $key => $value) { + if (is_array($value)) { + $filtered[$key] = $this->filterFiles($value); + } elseif ($value instanceof UploadedFile) { + if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) { + $filtered[$key] = new UploadedFile( + '', + $value->getClientOriginalName(), + $value->getClientMimeType(), + 0, + UPLOAD_ERR_INI_SIZE, + true + ); + } else { + $filtered[$key] = new UploadedFile( + $value->getPathname(), + $value->getClientOriginalName(), + $value->getClientMimeType(), + $value->getClientSize(), + $value->getError(), + true + ); + } + } + } + + return $filtered; + } + + /** + * Converts the HttpKernel response to a BrowserKit response. + * + * @param Response $response A Response instance + * + * @return DomResponse A DomResponse instance + */ + protected function filterResponse($response) + { + // this is needed to support StreamedResponse + ob_start(); + $response->sendContent(); + $content = ob_get_clean(); + + return new DomResponse($content, $response->getStatusCode(), $response->headers->all()); + } +} diff --git a/vendor/symfony/http-kernel/Config/EnvParametersResource.php b/vendor/symfony/http-kernel/Config/EnvParametersResource.php new file mode 100644 index 00000000..0fe6aca8 --- /dev/null +++ b/vendor/symfony/http-kernel/Config/EnvParametersResource.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\HttpKernel\Config; + +use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; + +/** + * EnvParametersResource represents resources stored in prefixed environment variables. + * + * @author Chris Wilkinson + */ +class EnvParametersResource implements SelfCheckingResourceInterface, \Serializable +{ + /** + * @var string + */ + private $prefix; + + /** + * @var string + */ + private $variables; + + /** + * Constructor. + * + * @param string $prefix + */ + public function __construct($prefix) + { + $this->prefix = $prefix; + $this->variables = $this->findVariables(); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return serialize($this->getResource()); + } + + /** + * @return array An array with two keys: 'prefix' for the prefix used and 'variables' containing all the variables watched by this resource + */ + public function getResource() + { + return array('prefix' => $this->prefix, 'variables' => $this->variables); + } + + /** + * {@inheritdoc} + */ + public function isFresh($timestamp) + { + return $this->findVariables() === $this->variables; + } + + public function serialize() + { + return serialize(array('prefix' => $this->prefix, 'variables' => $this->variables)); + } + + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $unserialized = unserialize($serialized, array('allowed_classes' => false)); + } else { + $unserialized = unserialize($serialized); + } + + $this->prefix = $unserialized['prefix']; + $this->variables = $unserialized['variables']; + } + + private function findVariables() + { + $variables = array(); + + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, $this->prefix)) { + $variables[$key] = $value; + } + } + + ksort($variables); + + return $variables; + } +} diff --git a/vendor/symfony/http-kernel/Config/FileLocator.php b/vendor/symfony/http-kernel/Config/FileLocator.php new file mode 100644 index 00000000..169c9ad6 --- /dev/null +++ b/vendor/symfony/http-kernel/Config/FileLocator.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\HttpKernel\Config; + +use Symfony\Component\Config\FileLocator as BaseFileLocator; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * FileLocator uses the KernelInterface to locate resources in bundles. + * + * @author Fabien Potencier + */ +class FileLocator extends BaseFileLocator +{ + private $kernel; + private $path; + + /** + * Constructor. + * + * @param KernelInterface $kernel A KernelInterface instance + * @param null|string $path The path the global resource directory + * @param array $paths An array of paths where to look for resources + */ + public function __construct(KernelInterface $kernel, $path = null, array $paths = array()) + { + $this->kernel = $kernel; + if (null !== $path) { + $this->path = $path; + $paths[] = $path; + } + + parent::__construct($paths); + } + + /** + * {@inheritdoc} + */ + public function locate($file, $currentPath = null, $first = true) + { + if (isset($file[0]) && '@' === $file[0]) { + return $this->kernel->locateResource($file, $this->path, $first); + } + + return parent::locate($file, $currentPath, $first); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver.php new file mode 100644 index 00000000..2c17125c --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface; + +/** + * Responsible for resolving the arguments passed to an action. + * + * @author Iltar van der Berg + */ +final class ArgumentResolver implements ArgumentResolverInterface +{ + private $argumentMetadataFactory; + + /** + * @var iterable|ArgumentValueResolverInterface[] + */ + private $argumentValueResolvers; + + public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, $argumentValueResolvers = array()) + { + $this->argumentMetadataFactory = $argumentMetadataFactory ?: new ArgumentMetadataFactory(); + $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + $arguments = array(); + + foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) { + foreach ($this->argumentValueResolvers as $resolver) { + if (!$resolver->supports($request, $metadata)) { + continue; + } + + $resolved = $resolver->resolve($request, $metadata); + + if (!$resolved instanceof \Generator) { + throw new \InvalidArgumentException(sprintf('%s::resolve() must yield at least one value.', get_class($resolver))); + } + + foreach ($resolved as $append) { + $arguments[] = $append; + } + + // continue to the next controller argument + continue 2; + } + + $representative = $controller; + + if (is_array($representative)) { + $representative = sprintf('%s::%s()', get_class($representative[0]), $representative[1]); + } elseif (is_object($representative)) { + $representative = get_class($representative); + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName())); + } + + return $arguments; + } + + public static function getDefaultArgumentValueResolvers() + { + return array( + new RequestAttributeValueResolver(), + new RequestValueResolver(), + new SessionValueResolver(), + new DefaultValueResolver(), + new VariadicValueResolver(), + ); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php new file mode 100644 index 00000000..e58fd3ab --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the default value defined in the action signature when no value has been given. + * + * @author Iltar van der Berg + */ +final class DefaultValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php new file mode 100644 index 00000000..05be372d --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a non-variadic argument's value from the request attributes. + * + * @author Iltar van der Berg + */ +final class RequestAttributeValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return !$argument->isVariadic() && $request->attributes->has($argument->getName()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $request->attributes->get($argument->getName()); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php new file mode 100644 index 00000000..2a5060a6 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the same instance as the request object passed along. + * + * @author Iltar van der Berg + */ +final class RequestValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $request; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php new file mode 100644 index 00000000..b1da6a9a --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a service keyed by _controller and argument name. + * + * @author Nicolas Grekas + */ +final class ServiceValueResolver implements ArgumentValueResolverInterface +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return is_string($controller = $request->attributes->get('_controller')) && $this->container->has($controller) && $this->container->get($controller)->has($argument->getName()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $this->container->get($request->attributes->get('_controller'))->get($argument->getName()); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php new file mode 100644 index 00000000..9e656d28 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields the Session. + * + * @author Iltar van der Berg + */ +final class SessionValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + $type = $argument->getType(); + if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) { + return false; + } + + return $request->getSession() instanceof $type; + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + yield $request->getSession(); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php b/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php new file mode 100644 index 00000000..56ae5f19 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Yields a variadic argument's values from the request attributes. + * + * @author Iltar van der Berg + */ +final class VariadicValueResolver implements ArgumentValueResolverInterface +{ + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument) + { + return $argument->isVariadic() && $request->attributes->has($argument->getName()); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument) + { + $values = $request->attributes->get($argument->getName()); + + if (!is_array($values)) { + throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), gettype($values))); + } + + foreach ($values as $value) { + yield $value; + } + } +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php b/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php new file mode 100644 index 00000000..5c512309 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentResolverInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * An ArgumentResolverInterface instance knows how to determine the + * arguments for a specific action. + * + * @author Fabien Potencier + */ +interface ArgumentResolverInterface +{ + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request + * @param callable $controller + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When no value could be provided for a required argument + */ + public function getArguments(Request $request, $controller); +} diff --git a/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php b/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php new file mode 100644 index 00000000..fd7b09ec --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ArgumentValueResolverInterface.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\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Responsible for resolving the value of an argument based on its metadata. + * + * @author Iltar van der Berg + */ +interface ArgumentValueResolverInterface +{ + /** + * Whether this resolver can resolve the value for the given ArgumentMetadata. + * + * @param Request $request + * @param ArgumentMetadata $argument + * + * @return bool + */ + public function supports(Request $request, ArgumentMetadata $argument); + + /** + * Returns the possible value(s). + * + * @param Request $request + * @param ArgumentMetadata $argument + * + * @return \Generator + */ + public function resolve(Request $request, ArgumentMetadata $argument); +} diff --git a/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php b/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php new file mode 100644 index 00000000..1a107c62 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ContainerControllerResolver.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; + +/** + * A controller resolver searching for a controller in a psr-11 container when using the "service:method" notation. + * + * @author Fabien Potencier + * @author Maxime Steinhausser + */ +class ContainerControllerResolver extends ControllerResolver +{ + protected $container; + + public function __construct(ContainerInterface $container, LoggerInterface $logger = null) + { + $this->container = $container; + + parent::__construct($logger); + } + + /** + * Returns a callable for the given controller. + * + * @param string $controller A Controller string + * + * @return mixed A PHP callable + * + * @throws \LogicException When the name could not be parsed + * @throws \InvalidArgumentException When the controller class does not exist + */ + protected function createController($controller) + { + if (false !== strpos($controller, '::')) { + return parent::createController($controller); + } + + if (1 == substr_count($controller, ':')) { + // controller in the "service:method" notation + list($service, $method) = explode(':', $controller, 2); + + return array($this->container->get($service), $method); + } + + if ($this->container->has($controller) && method_exists($service = $this->container->get($controller), '__invoke')) { + // invokable controller in the "service" notation + return $service; + } + + throw new \LogicException(sprintf('Unable to parse the controller name "%s".', $controller)); + } + + /** + * {@inheritdoc} + */ + protected function instantiateController($class) + { + if ($this->container->has($class)) { + return $this->container->get($class); + } + + return parent::instantiateController($class); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerReference.php b/vendor/symfony/http-kernel/Controller/ControllerReference.php new file mode 100644 index 00000000..3d1592e8 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerReference.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +/** + * Acts as a marker and a data holder for a Controller. + * + * Some methods in Symfony accept both a URI (as a string) or a controller as + * an argument. In the latter case, instead of passing an array representing + * the controller, you can use an instance of this class. + * + * @author Fabien Potencier + * + * @see FragmentRendererInterface + */ +class ControllerReference +{ + public $controller; + public $attributes = array(); + public $query = array(); + + /** + * Constructor. + * + * @param string $controller The controller name + * @param array $attributes An array of parameters to add to the Request attributes + * @param array $query An array of parameters to add to the Request query string + */ + public function __construct($controller, array $attributes = array(), array $query = array()) + { + $this->controller = $controller; + $this->attributes = $attributes; + $this->query = $query; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolver.php b/vendor/symfony/http-kernel/Controller/ControllerResolver.php new file mode 100644 index 00000000..f51a5a8e --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolver.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * ControllerResolver. + * + * This implementation uses the '_controller' request attribute to determine + * the controller to execute and uses the request attributes to determine + * the controller method arguments. + * + * @author Fabien Potencier + */ +class ControllerResolver implements ArgumentResolverInterface, ControllerResolverInterface +{ + private $logger; + + /** + * If the ...$arg functionality is available. + * + * Requires at least PHP 5.6.0 or HHVM 3.9.1 + * + * @var bool + */ + private $supportsVariadic; + + /** + * If scalar types exists. + * + * @var bool + */ + private $supportsScalarTypes; + + /** + * Constructor. + * + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + + $this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic'); + $this->supportsScalarTypes = method_exists('ReflectionParameter', 'getType'); + } + + /** + * {@inheritdoc} + * + * This method looks for a '_controller' request attribute that represents + * the controller name (a string like ClassName::MethodName). + */ + public function getController(Request $request) + { + if (!$controller = $request->attributes->get('_controller')) { + if (null !== $this->logger) { + $this->logger->warning('Unable to look for the controller as the "_controller" parameter is missing.'); + } + + return false; + } + + if (is_array($controller)) { + return $controller; + } + + if (is_object($controller)) { + if (method_exists($controller, '__invoke')) { + return $controller; + } + + throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', get_class($controller), $request->getPathInfo())); + } + + if (false === strpos($controller, ':')) { + if (method_exists($controller, '__invoke')) { + return $this->instantiateController($controller); + } elseif (function_exists($controller)) { + return $controller; + } + } + + $callable = $this->createController($controller); + + if (!is_callable($callable)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $this->getControllerError($callable))); + } + + return $callable; + } + + /** + * {@inheritdoc} + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead. + */ + public function getArguments(Request $request, $controller) + { + @trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED); + + if (is_array($controller)) { + $r = new \ReflectionMethod($controller[0], $controller[1]); + } elseif (is_object($controller) && !$controller instanceof \Closure) { + $r = new \ReflectionObject($controller); + $r = $r->getMethod('__invoke'); + } else { + $r = new \ReflectionFunction($controller); + } + + return $this->doGetArguments($request, $controller, $r->getParameters()); + } + + /** + * @param Request $request + * @param callable $controller + * @param \ReflectionParameter[] $parameters + * + * @return array The arguments to use when calling the action + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead. + */ + protected function doGetArguments(Request $request, $controller, array $parameters) + { + @trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), E_USER_DEPRECATED); + + $attributes = $request->attributes->all(); + $arguments = array(); + foreach ($parameters as $param) { + if (array_key_exists($param->name, $attributes)) { + if ($this->supportsVariadic && $param->isVariadic() && is_array($attributes[$param->name])) { + $arguments = array_merge($arguments, array_values($attributes[$param->name])); + } else { + $arguments[] = $attributes[$param->name]; + } + } elseif ($param->getClass() && $param->getClass()->isInstance($request)) { + $arguments[] = $request; + } elseif ($param->isDefaultValueAvailable()) { + $arguments[] = $param->getDefaultValue(); + } elseif ($this->supportsScalarTypes && $param->hasType() && $param->allowsNull()) { + $arguments[] = null; + } else { + if (is_array($controller)) { + $repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]); + } elseif (is_object($controller)) { + $repr = get_class($controller); + } else { + $repr = $controller; + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name)); + } + } + + return $arguments; + } + + /** + * Returns a callable for the given controller. + * + * @param string $controller A Controller string + * + * @return callable A PHP callable + * + * @throws \InvalidArgumentException + */ + protected function createController($controller) + { + if (false === strpos($controller, '::')) { + throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller)); + } + + list($class, $method) = explode('::', $controller, 2); + + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + return array($this->instantiateController($class), $method); + } + + /** + * Returns an instantiated controller. + * + * @param string $class A class name + * + * @return object + */ + protected function instantiateController($class) + { + return new $class(); + } + + private function getControllerError($callable) + { + if (is_string($callable)) { + if (false !== strpos($callable, '::')) { + $callable = explode('::', $callable); + } + + if (class_exists($callable) && !method_exists($callable, '__invoke')) { + return sprintf('Class "%s" does not have a method "__invoke".', $callable); + } + + if (!function_exists($callable)) { + return sprintf('Function "%s" does not exist.', $callable); + } + } + + if (!is_array($callable)) { + return sprintf('Invalid type for controller given, expected string or array, got "%s".', gettype($callable)); + } + + if (2 !== count($callable)) { + return sprintf('Invalid format for controller, expected array(controller, method) or controller::method.'); + } + + list($controller, $method) = $callable; + + if (is_string($controller) && !class_exists($controller)) { + return sprintf('Class "%s" does not exist.', $controller); + } + + $className = is_object($controller) ? get_class($controller) : $controller; + + if (method_exists($controller, $method)) { + return sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className); + } + + $collection = get_class_methods($controller); + + $alternatives = array(); + + foreach ($collection as $item) { + $lev = levenshtein($method, $item); + + if ($lev <= strlen($method) / 3 || false !== strpos($item, $method)) { + $alternatives[] = $item; + } + } + + asort($alternatives); + + $message = sprintf('Expected method "%s" on class "%s"', $method, $className); + + if (count($alternatives) > 0) { + $message .= sprintf(', did you mean "%s"?', implode('", "', $alternatives)); + } else { + $message .= sprintf('. Available methods: "%s".', implode('", "', $collection)); + } + + return $message; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php new file mode 100644 index 00000000..0dd7cce9 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * A ControllerResolverInterface implementation knows how to determine the + * controller to execute based on a Request object. + * + * It can also determine the arguments to pass to the Controller. + * + * A Controller can be any valid PHP callable. + * + * @author Fabien Potencier + */ +interface ControllerResolverInterface +{ + /** + * Returns the Controller instance associated with a Request. + * + * As several resolvers can exist for a single application, a resolver must + * return false when it is not able to determine the controller. + * + * The resolver must only throw an exception when it should be able to load + * controller but cannot because of some errors made by the developer. + * + * @param Request $request A Request instance + * + * @return callable|false A PHP callable representing the Controller, + * or false if this resolver is not able to determine the controller + * + * @throws \LogicException If the controller can't be found + */ + public function getController(Request $request); + + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request A Request instance + * @param callable $controller A PHP callable + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When value for argument given is not provided + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Please use the {@see ArgumentResolverInterface} instead. + */ + public function getArguments(Request $request, $controller); +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php b/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php new file mode 100644 index 00000000..6fb0fa66 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableArgumentResolver.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Fabien Potencier + */ +class TraceableArgumentResolver implements ArgumentResolverInterface +{ + private $resolver; + private $stopwatch; + + public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stopwatch) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + $e = $this->stopwatch->start('controller.get_arguments'); + + $ret = $this->resolver->getArguments($request, $controller); + + $e->stop(); + + return $ret; + } +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php new file mode 100644 index 00000000..ce291b1e --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpFoundation\Request; + +/** + * TraceableControllerResolver. + * + * @author Fabien Potencier + */ +class TraceableControllerResolver implements ControllerResolverInterface, ArgumentResolverInterface +{ + private $resolver; + private $stopwatch; + private $argumentResolver; + + /** + * Constructor. + * + * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param ArgumentResolverInterface $argumentResolver Only required for BC + */ + public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch, ArgumentResolverInterface $argumentResolver = null) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + $this->argumentResolver = $argumentResolver; + + // BC + if (null === $this->argumentResolver) { + $this->argumentResolver = $resolver; + } + + if (!$this->argumentResolver instanceof TraceableArgumentResolver) { + $this->argumentResolver = new TraceableArgumentResolver($this->argumentResolver, $this->stopwatch); + } + } + + /** + * {@inheritdoc} + */ + public function getController(Request $request) + { + $e = $this->stopwatch->start('controller.get_callable'); + + $ret = $this->resolver->getController($request); + + $e->stop(); + + return $ret; + } + + /** + * {@inheritdoc} + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. + */ + public function getArguments(Request $request, $controller) + { + @trigger_error(sprintf('The %s method is deprecated as of 3.1 and will be removed in 4.0. Please use the %s instead.', __METHOD__, TraceableArgumentResolver::class), E_USER_DEPRECATED); + + $ret = $this->argumentResolver->getArguments($request, $controller); + + return $ret; + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php new file mode 100644 index 00000000..32316a8d --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.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\HttpKernel\ControllerMetadata; + +/** + * Responsible for storing metadata of an argument. + * + * @author Iltar van der Berg + */ +class ArgumentMetadata +{ + private $name; + private $type; + private $isVariadic; + private $hasDefaultValue; + private $defaultValue; + private $isNullable; + + /** + * @param string $name + * @param string $type + * @param bool $isVariadic + * @param bool $hasDefaultValue + * @param mixed $defaultValue + * @param bool $isNullable + */ + public function __construct($name, $type, $isVariadic, $hasDefaultValue, $defaultValue, $isNullable = false) + { + $this->name = $name; + $this->type = $type; + $this->isVariadic = $isVariadic; + $this->hasDefaultValue = $hasDefaultValue; + $this->defaultValue = $defaultValue; + $this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue); + } + + /** + * Returns the name as given in PHP, $foo would yield "foo". + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the type of the argument. + * + * The type is the PHP class in 5.5+ and additionally the basic type in PHP 7.0+. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Returns whether the argument is defined as "...$variadic". + * + * @return bool + */ + public function isVariadic() + { + return $this->isVariadic; + } + + /** + * Returns whether the argument has a default value. + * + * Implies whether an argument is optional. + * + * @return bool + */ + public function hasDefaultValue() + { + return $this->hasDefaultValue; + } + + /** + * Returns whether the argument accepts null values. + * + * @return bool + */ + public function isNullable() + { + return $this->isNullable; + } + + /** + * Returns the default value of the argument. + * + * @throws \LogicException if no default value is present; {@see self::hasDefaultValue()} + * + * @return mixed + */ + public function getDefaultValue() + { + if (!$this->hasDefaultValue) { + throw new \LogicException(sprintf('Argument $%s does not have a default value. Use %s::hasDefaultValue() to avoid this exception.', $this->name, __CLASS__)); + } + + return $this->defaultValue; + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php new file mode 100644 index 00000000..d1e7af20 --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\ControllerMetadata; + +/** + * Builds {@see ArgumentMetadata} objects based on the given Controller. + * + * @author Iltar van der Berg + */ +final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface +{ + /** + * If the ...$arg functionality is available. + * + * Requires at least PHP 5.6.0 or HHVM 3.9.1 + * + * @var bool + */ + private $supportsVariadic; + + /** + * If the reflection supports the getType() method to resolve types. + * + * Requires at least PHP 7.0.0 or HHVM 3.11.0 + * + * @var bool + */ + private $supportsParameterType; + + public function __construct() + { + $this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic'); + $this->supportsParameterType = method_exists('ReflectionParameter', 'getType'); + } + + /** + * {@inheritdoc} + */ + public function createArgumentMetadata($controller) + { + $arguments = array(); + + if (is_array($controller)) { + $reflection = new \ReflectionMethod($controller[0], $controller[1]); + } elseif (is_object($controller) && !$controller instanceof \Closure) { + $reflection = (new \ReflectionObject($controller))->getMethod('__invoke'); + } else { + $reflection = new \ReflectionFunction($controller); + } + + foreach ($reflection->getParameters() as $param) { + $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param), $param->allowsNull()); + } + + return $arguments; + } + + /** + * Returns whether an argument is variadic. + * + * @param \ReflectionParameter $parameter + * + * @return bool + */ + private function isVariadic(\ReflectionParameter $parameter) + { + return $this->supportsVariadic && $parameter->isVariadic(); + } + + /** + * Determines whether an argument has a default value. + * + * @param \ReflectionParameter $parameter + * + * @return bool + */ + private function hasDefaultValue(\ReflectionParameter $parameter) + { + return $parameter->isDefaultValueAvailable(); + } + + /** + * Returns a default value if available. + * + * @param \ReflectionParameter $parameter + * + * @return mixed|null + */ + private function getDefaultValue(\ReflectionParameter $parameter) + { + return $this->hasDefaultValue($parameter) ? $parameter->getDefaultValue() : null; + } + + /** + * Returns an associated type to the given parameter if available. + * + * @param \ReflectionParameter $parameter + * + * @return null|string + */ + private function getType(\ReflectionParameter $parameter) + { + if ($this->supportsParameterType) { + if (!$type = $parameter->getType()) { + return; + } + $typeName = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString(); + if ('array' === $typeName && !$type->isBuiltin()) { + // Special case for HHVM with variadics + return; + } + + return $typeName; + } + + if (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $parameter, $info)) { + return $info[1]; + } + } +} diff --git a/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php new file mode 100644 index 00000000..6ea179d7 --- /dev/null +++ b/vendor/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.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\HttpKernel\ControllerMetadata; + +/** + * Builds method argument data. + * + * @author Iltar van der Berg + */ +interface ArgumentMetadataFactoryInterface +{ + /** + * @param mixed $controller The controller to resolve the arguments for + * + * @return ArgumentMetadata[] + */ + public function createArgumentMetadata($controller); +} diff --git a/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php new file mode 100644 index 00000000..b8405d59 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.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\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * AjaxDataCollector. + * + * @author Bart van den Burg + */ +class AjaxDataCollector extends DataCollector +{ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // all collecting is done client side + } + + public function getName() + { + return 'ajax'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php new file mode 100644 index 00000000..87c4c08e --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php @@ -0,0 +1,330 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Caster\LinkStub; + +/** + * ConfigDataCollector. + * + * @author Fabien Potencier + */ +class ConfigDataCollector extends DataCollector implements LateDataCollectorInterface +{ + /** + * @var KernelInterface + */ + private $kernel; + private $name; + private $version; + private $hasVarDumper; + + /** + * Constructor. + * + * @param string $name The name of the application using the web profiler + * @param string $version The version of the application using the web profiler + */ + public function __construct($name = null, $version = null) + { + $this->name = $name; + $this->version = $version; + $this->hasVarDumper = class_exists(LinkStub::class); + } + + /** + * Sets the Kernel associated with this Request. + * + * @param KernelInterface $kernel A KernelInterface instance + */ + public function setKernel(KernelInterface $kernel = null) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'app_name' => $this->name, + 'app_version' => $this->version, + 'token' => $response->headers->get('X-Debug-Token'), + 'symfony_version' => Kernel::VERSION, + 'symfony_state' => 'unknown', + 'name' => isset($this->kernel) ? $this->kernel->getName() : 'n/a', + 'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a', + 'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a', + 'php_version' => PHP_VERSION, + 'php_architecture' => PHP_INT_SIZE * 8, + 'php_intl_locale' => class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', + 'php_timezone' => date_default_timezone_get(), + 'xdebug_enabled' => extension_loaded('xdebug'), + 'apcu_enabled' => extension_loaded('apcu') && ini_get('apc.enabled'), + 'zend_opcache_enabled' => extension_loaded('Zend OPcache') && ini_get('opcache.enable'), + 'bundles' => array(), + 'sapi_name' => PHP_SAPI, + ); + + if (isset($this->kernel)) { + foreach ($this->kernel->getBundles() as $name => $bundle) { + $this->data['bundles'][$name] = $this->hasVarDumper ? new LinkStub($bundle->getPath()) : $bundle->getPath(); + } + + $this->data['symfony_state'] = $this->determineSymfonyState(); + $this->data['symfony_minor_version'] = sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION); + $eom = \DateTime::createFromFormat('m/Y', Kernel::END_OF_MAINTENANCE); + $eol = \DateTime::createFromFormat('m/Y', Kernel::END_OF_LIFE); + $this->data['symfony_eom'] = $eom->format('F Y'); + $this->data['symfony_eol'] = $eol->format('F Y'); + } + + if (preg_match('~^(\d+(?:\.\d+)*)(.+)?$~', $this->data['php_version'], $matches) && isset($matches[2])) { + $this->data['php_version'] = $matches[1]; + $this->data['php_version_extra'] = $matches[2]; + } + } + + public function lateCollect() + { + $this->data = $this->cloneVar($this->data); + } + + public function getApplicationName() + { + return $this->data['app_name']; + } + + public function getApplicationVersion() + { + return $this->data['app_version']; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() + { + return $this->data['token']; + } + + /** + * Gets the Symfony version. + * + * @return string The Symfony version + */ + public function getSymfonyVersion() + { + return $this->data['symfony_version']; + } + + /** + * Returns the state of the current Symfony release. + * + * @return string One of: unknown, dev, stable, eom, eol + */ + public function getSymfonyState() + { + return $this->data['symfony_state']; + } + + /** + * Returns the minor Symfony version used (without patch numbers of extra + * suffix like "RC", "beta", etc.). + * + * @return string + */ + public function getSymfonyMinorVersion() + { + return $this->data['symfony_minor_version']; + } + + /** + * Returns the human redable date when this Symfony version ends its + * maintenance period. + * + * @return string + */ + public function getSymfonyEom() + { + return $this->data['symfony_eom']; + } + + /** + * Returns the human redable date when this Symfony version reaches its + * "end of life" and won't receive bugs or security fixes. + * + * @return string + */ + public function getSymfonyEol() + { + return $this->data['symfony_eol']; + } + + /** + * Gets the PHP version. + * + * @return string The PHP version + */ + public function getPhpVersion() + { + return $this->data['php_version']; + } + + /** + * Gets the PHP version extra part. + * + * @return string|null The extra part + */ + public function getPhpVersionExtra() + { + return isset($this->data['php_version_extra']) ? $this->data['php_version_extra'] : null; + } + + /** + * @return int The PHP architecture as number of bits (e.g. 32 or 64) + */ + public function getPhpArchitecture() + { + return $this->data['php_architecture']; + } + + /** + * @return string + */ + public function getPhpIntlLocale() + { + return $this->data['php_intl_locale']; + } + + /** + * @return string + */ + public function getPhpTimezone() + { + return $this->data['php_timezone']; + } + + /** + * Gets the application name. + * + * @return string The application name + */ + public function getAppName() + { + return $this->data['name']; + } + + /** + * Gets the environment. + * + * @return string The environment + */ + public function getEnv() + { + return $this->data['env']; + } + + /** + * Returns true if the debug is enabled. + * + * @return bool true if debug is enabled, false otherwise + */ + public function isDebug() + { + return $this->data['debug']; + } + + /** + * Returns true if the XDebug is enabled. + * + * @return bool true if XDebug is enabled, false otherwise + */ + public function hasXDebug() + { + return $this->data['xdebug_enabled']; + } + + /** + * Returns true if APCu is enabled. + * + * @return bool true if APCu is enabled, false otherwise + */ + public function hasApcu() + { + return $this->data['apcu_enabled']; + } + + /** + * Returns true if Zend OPcache is enabled. + * + * @return bool true if Zend OPcache is enabled, false otherwise + */ + public function hasZendOpcache() + { + return $this->data['zend_opcache_enabled']; + } + + public function getBundles() + { + return $this->data['bundles']; + } + + /** + * Gets the PHP SAPI name. + * + * @return string The environment + */ + public function getSapiName() + { + return $this->data['sapi_name']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'config'; + } + + /** + * Tries to retrieve information about the current Symfony version. + * + * @return string One of: dev, stable, eom, eol + */ + private function determineSymfonyState() + { + $now = new \DateTime(); + $eom = \DateTime::createFromFormat('m/Y', Kernel::END_OF_MAINTENANCE)->modify('last day of this month'); + $eol = \DateTime::createFromFormat('m/Y', Kernel::END_OF_LIFE)->modify('last day of this month'); + + if ($now > $eol) { + $versionState = 'eol'; + } elseif ($now > $eom) { + $versionState = 'eom'; + } elseif ('' !== Kernel::EXTRA_VERSION) { + $versionState = 'dev'; + } else { + $versionState = 'stable'; + } + + return $versionState; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollector.php b/vendor/symfony/http-kernel/DataCollector/DataCollector.php new file mode 100644 index 00000000..0d574eae --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollector.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\HttpKernel\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * DataCollector. + * + * Children of this class must store the collected data in the data property. + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +abstract class DataCollector implements DataCollectorInterface, \Serializable +{ + protected $data = array(); + + /** + * @var ValueExporter + */ + private $valueExporter; + + /** + * @var ClonerInterface + */ + private static $cloner; + + public function serialize() + { + return serialize($this->data); + } + + public function unserialize($data) + { + $this->data = unserialize($data); + } + + /** + * Converts the variable into a serializable Data instance. + * + * This array can be displayed in the template using + * the VarDumper component. + * + * @param mixed $var + * + * @return Data + */ + protected function cloneVar($var) + { + if (null === self::$cloner) { + if (class_exists(ClassStub::class)) { + self::$cloner = new VarCloner(); + self::$cloner->setMaxItems(-1); + } else { + @trigger_error(sprintf('Using the %s() method without the VarDumper component is deprecated since version 3.2 and won\'t be supported in 4.0. Install symfony/var-dumper version 3.2 or above.', __METHOD__), E_USER_DEPRECATED); + self::$cloner = false; + } + } + if (false === self::$cloner) { + if (null === $this->valueExporter) { + $this->valueExporter = new ValueExporter(); + } + + return $this->valueExporter->exportValue($var); + } + + return self::$cloner->cloneVar($var); + } + + /** + * Converts a PHP variable to a string. + * + * @param mixed $var A PHP variable + * + * @return string The string representation of the variable + * + * @deprecated since version 3.2, to be removed in 4.0. Use cloneVar() instead. + */ + protected function varToString($var) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use cloneVar() instead.', __METHOD__), E_USER_DEPRECATED); + + if (null === $this->valueExporter) { + $this->valueExporter = new ValueExporter(); + } + + return $this->valueExporter->exportValue($var); + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php new file mode 100644 index 00000000..2820ad5b --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * DataCollectorInterface. + * + * @author Fabien Potencier + */ +interface DataCollectorInterface +{ + /** + * Collects data for the given Request and Response. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * @param \Exception $exception An Exception instance + */ + public function collect(Request $request, Response $response, \Exception $exception = null); + + /** + * Returns the name of the collector. + * + * @return string The collector name + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php new file mode 100644 index 00000000..b50386cb --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Twig\Template; + +/** + * @author Nicolas Grekas + */ +class DumpDataCollector extends DataCollector implements DataDumperInterface +{ + private $stopwatch; + private $fileLinkFormat; + private $dataCount = 0; + private $isCollected = true; + private $clonesCount = 0; + private $clonesIndex = 0; + private $rootRefs; + private $charset; + private $requestStack; + private $dumper; + private $dumperIsInjected; + + public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null) + { + $this->stopwatch = $stopwatch; + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'; + $this->requestStack = $requestStack; + $this->dumper = $dumper; + $this->dumperIsInjected = null !== $dumper; + + // All clones share these properties by reference: + $this->rootRefs = array( + &$this->data, + &$this->dataCount, + &$this->isCollected, + &$this->clonesCount, + ); + } + + public function __clone() + { + $this->clonesIndex = ++$this->clonesCount; + } + + public function dump(Data $data) + { + if ($this->stopwatch) { + $this->stopwatch->start('dump'); + } + if ($this->isCollected) { + $this->isCollected = false; + } + + $trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 7); + + $file = $trace[0]['file']; + $line = $trace[0]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 1; $i < 7; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class'] + ) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + while (++$i < 7) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = array(); + + 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); + } + + if ($this->dumper) { + $this->doDump($data, $name, $file, $line); + } + + $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt'); + ++$this->dataCount; + + if ($this->stopwatch) { + $this->stopwatch->stop('dump'); + } + } + + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // Sub-requests and programmatic calls stay in the collected profile. + if ($this->dumper || ($this->requestStack && $this->requestStack->getMasterRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) { + return; + } + + // In all other conditions that remove the web debug toolbar, dumps are written on the output. + if (!$this->requestStack + || !$response->headers->has('X-Debug-Token') + || $response->isRedirection() + || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html')) + || 'html' !== $request->getRequestFormat() + || false === strripos($response->getContent(), '') + ) { + if ($response->headers->has('Content-Type') && false !== strpos($response->headers->get('Content-Type'), 'html')) { + $this->dumper = new HtmlDumper('php://output', $this->charset); + $this->dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat)); + } else { + $this->dumper = new CliDumper('php://output', $this->charset); + } + + foreach ($this->data as $dump) { + $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + } + } + } + + public function serialize() + { + if ($this->clonesCount !== $this->clonesIndex) { + return 'a:0:{}'; + } + + $this->data[] = $this->fileLinkFormat; + $this->data[] = $this->charset; + $ser = serialize($this->data); + $this->data = array(); + $this->dataCount = 0; + $this->isCollected = true; + if (!$this->dumperIsInjected) { + $this->dumper = null; + } + + return $ser; + } + + public function unserialize($data) + { + parent::unserialize($data); + $charset = array_pop($this->data); + $fileLinkFormat = array_pop($this->data); + $this->dataCount = count($this->data); + self::__construct($this->stopwatch, $fileLinkFormat, $charset); + } + + public function getDumpsCount() + { + return $this->dataCount; + } + + public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1) + { + $data = fopen('php://memory', 'r+b'); + + if ('html' === $format) { + $dumper = new HtmlDumper($data, $this->charset); + $dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat)); + } else { + throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format)); + } + $dumps = array(); + + foreach ($this->data as $dump) { + $dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth)); + $dump['data'] = stream_get_contents($data, -1, 0); + ftruncate($data, 0); + rewind($data); + $dumps[] = $dump; + } + + return $dumps; + } + + public function getName() + { + return 'dump'; + } + + public function __destruct() + { + if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) { + $this->clonesCount = 0; + $this->isCollected = true; + + $h = headers_list(); + $i = count($h); + array_unshift($h, 'Content-Type: '.ini_get('default_mimetype')); + while (0 !== stripos($h[$i], 'Content-Type:')) { + --$i; + } + + if ('cli' !== PHP_SAPI && stripos($h[$i], 'html')) { + $this->dumper = new HtmlDumper('php://output', $this->charset); + $this->dumper->setDisplayOptions(array('fileLinkFormat' => $this->fileLinkFormat)); + } else { + $this->dumper = new CliDumper('php://output', $this->charset); + } + + foreach ($this->data as $i => $dump) { + $this->data[$i] = null; + $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + } + + $this->data = array(); + $this->dataCount = 0; + } + } + + private function doDump($data, $name, $file, $line) + { + if ($this->dumper instanceof CliDumper) { + $contextDumper = function ($name, $file, $line, $fmt) { + if ($this instanceof HtmlDumper) { + if ($file) { + $s = $this->style('meta', '%s'); + $f = strip_tags($this->style('', $file)); + $name = strip_tags($this->style('', $name)); + if ($fmt && $link = is_string($fmt) ? strtr($fmt, array('%f' => $file, '%l' => $line)) : $fmt->format($file, $line)) { + $name = sprintf(''.$s.'', strip_tags($this->style('', $link)), $f, $name); + } else { + $name = sprintf(''.$s.'', $f, $name); + } + } else { + $name = $this->style('meta', $name); + } + $this->line = $name.' on line '.$this->style('meta', $line).':'; + } else { + $this->line = $this->style('meta', $name).' on line '.$this->style('meta', $line).':'; + } + $this->dumpLine(0); + }; + $contextDumper = $contextDumper->bindTo($this->dumper, $this->dumper); + $contextDumper($name, $file, $line, $this->fileLinkFormat); + } else { + $cloner = new VarCloner(); + $this->dumper->dump($cloner->cloneVar($name.' on line '.$line.':')); + } + $this->dumper->dump($data); + } + + private function htmlEncode($s) + { + $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/http-kernel/DataCollector/EventDataCollector.php b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php new file mode 100644 index 00000000..3d75f322 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.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\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; + +/** + * EventDataCollector. + * + * @author Fabien Potencier + */ +class EventDataCollector extends DataCollector implements LateDataCollectorInterface +{ + protected $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher = null) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'called_listeners' => array(), + 'not_called_listeners' => array(), + ); + } + + public function lateCollect() + { + if ($this->dispatcher instanceof TraceableEventDispatcherInterface) { + $this->setCalledListeners($this->dispatcher->getCalledListeners()); + $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners()); + } + $this->data = $this->cloneVar($this->data); + } + + /** + * Sets the called listeners. + * + * @param array $listeners An array of called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function setCalledListeners(array $listeners) + { + $this->data['called_listeners'] = $listeners; + } + + /** + * Gets the called listeners. + * + * @return array An array of called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function getCalledListeners() + { + return $this->data['called_listeners']; + } + + /** + * Sets the not called listeners. + * + * @param array $listeners An array of not called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function setNotCalledListeners(array $listeners) + { + $this->data['not_called_listeners'] = $listeners; + } + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function getNotCalledListeners() + { + return $this->data['not_called_listeners']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'events'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php new file mode 100644 index 00000000..9fe82644 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * ExceptionDataCollector. + * + * @author Fabien Potencier + */ +class ExceptionDataCollector extends DataCollector +{ + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $exception) { + $this->data = array( + 'exception' => FlattenException::create($exception), + ); + } + } + + /** + * Checks if the exception is not null. + * + * @return bool true if the exception is not null, false otherwise + */ + public function hasException() + { + return isset($this->data['exception']); + } + + /** + * Gets the exception. + * + * @return \Exception The exception + */ + public function getException() + { + return $this->data['exception']; + } + + /** + * Gets the exception message. + * + * @return string The exception message + */ + public function getMessage() + { + return $this->data['exception']->getMessage(); + } + + /** + * Gets the exception code. + * + * @return int The exception code + */ + public function getCode() + { + return $this->data['exception']->getCode(); + } + + /** + * Gets the status code. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->data['exception']->getStatusCode(); + } + + /** + * Gets the exception trace. + * + * @return array The exception trace + */ + public function getTrace() + { + return $this->data['exception']->getTrace(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'exception'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php new file mode 100644 index 00000000..012332de --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +/** + * LateDataCollectorInterface. + * + * @author Fabien Potencier + */ +interface LateDataCollectorInterface +{ + /** + * Collects data as late as possible. + */ + public function lateCollect(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php new file mode 100644 index 00000000..7ae1fbbd --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; + +/** + * LogDataCollector. + * + * @author Fabien Potencier + */ +class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private $logger; + private $containerPathPrefix; + + public function __construct($logger = null, $containerPathPrefix = null) + { + if (null !== $logger && $logger instanceof DebugLoggerInterface) { + $this->logger = $logger; + } + + $this->containerPathPrefix = $containerPathPrefix; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // everything is done as late as possible + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + if (null !== $this->logger) { + $this->data = $this->computeErrorsCount(); + + $containerDeprecationLogs = $this->getContainerDeprecationLogs(); + $this->data['deprecation_count'] += count($containerDeprecationLogs); + $this->data['compiler_logs'] = $this->getContainerCompilerLogs(); + $this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs(), $containerDeprecationLogs)); + $this->data = $this->cloneVar($this->data); + } + } + + /** + * Gets the logs. + * + * @return array An array of logs + */ + public function getLogs() + { + return isset($this->data['logs']) ? $this->data['logs'] : array(); + } + + public function getPriorities() + { + return isset($this->data['priorities']) ? $this->data['priorities'] : array(); + } + + public function countErrors() + { + return isset($this->data['error_count']) ? $this->data['error_count'] : 0; + } + + public function countDeprecations() + { + return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0; + } + + public function countWarnings() + { + return isset($this->data['warning_count']) ? $this->data['warning_count'] : 0; + } + + public function countScreams() + { + return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0; + } + + public function getCompilerLogs() + { + return isset($this->data['compiler_logs']) ? $this->data['compiler_logs'] : array(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'logger'; + } + + private function getContainerDeprecationLogs() + { + if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Deprecations.log')) { + return array(); + } + + $stubs = array(); + $bootTime = filemtime($file); + $logs = array(); + foreach (unserialize(file_get_contents($file)) as $log) { + $log['context'] = array('exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'])); + $log['timestamp'] = $bootTime; + $log['priority'] = 100; + $log['priorityName'] = 'DEBUG'; + $log['channel'] = '-'; + $log['scream'] = false; + $logs[] = $log; + } + + return $logs; + } + + private function getContainerCompilerLogs() + { + if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Compiler.log')) { + return array(); + } + + $logs = array(); + foreach (file($file, FILE_IGNORE_NEW_LINES) as $log) { + $log = explode(': ', $log, 2); + if (!isset($log[1]) || !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $log[0])) { + $log = array('Unknown Compiler Pass', implode(': ', $log)); + } + + $logs[$log[0]][] = array('message' => $log[1]); + } + + return $logs; + } + + private function sanitizeLogs($logs) + { + $sanitizedLogs = array(); + + foreach ($logs as $log) { + if (!$this->isSilencedOrDeprecationErrorLog($log)) { + $sanitizedLogs[] = $log; + + continue; + } + + $exception = $log['context']['exception']; + $errorId = md5("{$exception->getSeverity()}/{$exception->getLine()}/{$exception->getFile()}\0{$log['message']}", true); + + if (isset($sanitizedLogs[$errorId])) { + ++$sanitizedLogs[$errorId]['errorCount']; + } else { + $log += array( + 'errorCount' => 1, + 'scream' => $exception instanceof SilencedErrorContext, + ); + + $sanitizedLogs[$errorId] = $log; + } + } + + return array_values($sanitizedLogs); + } + + private function isSilencedOrDeprecationErrorLog(array $log) + { + if (!isset($log['context']['exception'])) { + return false; + } + + $exception = $log['context']['exception']; + + if ($exception instanceof SilencedErrorContext) { + return true; + } + + if ($exception instanceof \ErrorException && in_array($exception->getSeverity(), array(E_DEPRECATED, E_USER_DEPRECATED), true)) { + return true; + } + + return false; + } + + private function computeErrorsCount() + { + $count = array( + 'error_count' => $this->logger->countErrors(), + 'deprecation_count' => 0, + 'warning_count' => 0, + 'scream_count' => 0, + 'priorities' => array(), + ); + + foreach ($this->logger->getLogs() as $log) { + if (isset($count['priorities'][$log['priority']])) { + ++$count['priorities'][$log['priority']]['count']; + } else { + $count['priorities'][$log['priority']] = array( + 'count' => 1, + 'name' => $log['priorityName'], + ); + } + if ('WARNING' === $log['priorityName']) { + ++$count['warning_count']; + } + + if ($this->isSilencedOrDeprecationErrorLog($log)) { + if ($log['context']['exception'] instanceof SilencedErrorContext) { + ++$count['scream_count']; + } else { + ++$count['deprecation_count']; + } + } + } + + ksort($count['priorities']); + + return $count; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php new file mode 100644 index 00000000..93850108 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * MemoryDataCollector. + * + * @author Fabien Potencier + */ +class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface +{ + public function __construct() + { + $this->data = array( + 'memory' => 0, + 'memory_limit' => $this->convertToBytes(ini_get('memory_limit')), + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->updateMemoryUsage(); + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + $this->updateMemoryUsage(); + } + + /** + * Gets the memory. + * + * @return int The memory + */ + public function getMemory() + { + return $this->data['memory']; + } + + /** + * Gets the PHP memory limit. + * + * @return int The memory limit + */ + public function getMemoryLimit() + { + return $this->data['memory_limit']; + } + + /** + * Updates the memory usage data. + */ + public function updateMemoryUsage() + { + $this->data['memory'] = memory_get_peak_usage(true); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'memory'; + } + + private function convertToBytes($memoryLimit) + { + if ('-1' === $memoryLimit) { + return -1; + } + + $memoryLimit = strtolower($memoryLimit); + $max = strtolower(ltrim($memoryLimit, '+')); + if (0 === strpos($max, '0x')) { + $max = intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($memoryLimit, -1)) { + case 't': $max *= 1024; + case 'g': $max *= 1024; + case 'm': $max *= 1024; + case 'k': $max *= 1024; + } + + return $max; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php new file mode 100644 index 00000000..7049844f --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php @@ -0,0 +1,397 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * RequestDataCollector. + * + * @author Fabien Potencier + */ +class RequestDataCollector extends DataCollector implements EventSubscriberInterface, LateDataCollectorInterface +{ + /** @var \SplObjectStorage */ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // attributes are serialized and as they can be anything, they need to be converted to strings. + $attributes = array(); + $route = ''; + foreach ($request->attributes->all() as $key => $value) { + if ('_route' === $key) { + $route = is_object($value) ? $value->getPath() : $value; + $attributes[$key] = $route; + } else { + $attributes[$key] = $value; + } + } + + $content = null; + try { + $content = $request->getContent(); + } catch (\LogicException $e) { + // the user already got the request content as a resource + $content = false; + } + + $sessionMetadata = array(); + $sessionAttributes = array(); + $session = null; + $flashes = array(); + if ($request->hasSession()) { + $session = $request->getSession(); + if ($session->isStarted()) { + $sessionMetadata['Created'] = date(DATE_RFC822, $session->getMetadataBag()->getCreated()); + $sessionMetadata['Last used'] = date(DATE_RFC822, $session->getMetadataBag()->getLastUsed()); + $sessionMetadata['Lifetime'] = $session->getMetadataBag()->getLifetime(); + $sessionAttributes = $session->all(); + $flashes = $session->getFlashBag()->peekAll(); + } + } + + $statusCode = $response->getStatusCode(); + + $responseCookies = array(); + foreach ($response->headers->getCookies() as $cookie) { + $responseCookies[$cookie->getName()] = $cookie; + } + + $this->data = array( + 'method' => $request->getMethod(), + 'format' => $request->getRequestFormat(), + 'content' => $content, + 'content_type' => $response->headers->get('Content-Type', 'text/html'), + 'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '', + 'status_code' => $statusCode, + 'request_query' => $request->query->all(), + 'request_request' => $request->request->all(), + 'request_headers' => $request->headers->all(), + 'request_server' => $request->server->all(), + 'request_cookies' => $request->cookies->all(), + 'request_attributes' => $attributes, + 'route' => $route, + 'response_headers' => $response->headers->all(), + 'response_cookies' => $responseCookies, + 'session_metadata' => $sessionMetadata, + 'session_attributes' => $sessionAttributes, + 'flashes' => $flashes, + 'path_info' => $request->getPathInfo(), + 'controller' => 'n/a', + 'locale' => $request->getLocale(), + ); + + if (isset($this->data['request_headers']['php-auth-pw'])) { + $this->data['request_headers']['php-auth-pw'] = '******'; + } + + if (isset($this->data['request_server']['PHP_AUTH_PW'])) { + $this->data['request_server']['PHP_AUTH_PW'] = '******'; + } + + if (isset($this->data['request_request']['_password'])) { + $this->data['request_request']['_password'] = '******'; + } + + foreach ($this->data as $key => $value) { + if (!is_array($value)) { + continue; + } + if ('request_headers' === $key || 'response_headers' === $key) { + $this->data[$key] = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); + } + } + + if (isset($this->controllers[$request])) { + $this->data['controller'] = $this->parseController($this->controllers[$request]); + unset($this->controllers[$request]); + } + + if (null !== $session && $session->isStarted()) { + if ($request->attributes->has('_redirected')) { + $this->data['redirect'] = $session->remove('sf_redirect'); + } + + if ($response->isRedirect()) { + $session->set('sf_redirect', array( + 'token' => $response->headers->get('x-debug-token'), + 'route' => $request->attributes->get('_route', 'n/a'), + 'method' => $request->getMethod(), + 'controller' => $this->parseController($request->attributes->get('_controller')), + 'status_code' => $statusCode, + 'status_text' => Response::$statusTexts[(int) $statusCode], + )); + } + } + + $this->data['identifier'] = $this->data['route'] ?: (is_array($this->data['controller']) ? $this->data['controller']['class'].'::'.$this->data['controller']['method'].'()' : $this->data['controller']); + } + + public function lateCollect() + { + $this->data = $this->cloneVar($this->data); + } + + public function getMethod() + { + return $this->data['method']; + } + + public function getPathInfo() + { + return $this->data['path_info']; + } + + public function getRequestRequest() + { + return new ParameterBag($this->data['request_request']->getValue()); + } + + public function getRequestQuery() + { + return new ParameterBag($this->data['request_query']->getValue()); + } + + public function getRequestHeaders() + { + return new ParameterBag($this->data['request_headers']->getValue()); + } + + public function getRequestServer($raw = false) + { + return new ParameterBag($this->data['request_server']->getValue($raw)); + } + + public function getRequestCookies($raw = false) + { + return new ParameterBag($this->data['request_cookies']->getValue($raw)); + } + + public function getRequestAttributes() + { + return new ParameterBag($this->data['request_attributes']->getValue()); + } + + public function getResponseHeaders() + { + return new ParameterBag($this->data['response_headers']->getValue()); + } + + public function getResponseCookies() + { + return new ParameterBag($this->data['response_cookies']->getValue()); + } + + public function getSessionMetadata() + { + return $this->data['session_metadata']->getValue(); + } + + public function getSessionAttributes() + { + return $this->data['session_attributes']->getValue(); + } + + public function getFlashes() + { + return $this->data['flashes']->getValue(); + } + + public function getContent() + { + return $this->data['content']; + } + + public function getContentType() + { + return $this->data['content_type']; + } + + public function getStatusText() + { + return $this->data['status_text']; + } + + public function getStatusCode() + { + return $this->data['status_code']; + } + + public function getFormat() + { + return $this->data['format']; + } + + public function getLocale() + { + return $this->data['locale']; + } + + /** + * Gets the route name. + * + * The _route request attributes is automatically set by the Router Matcher. + * + * @return string The route + */ + public function getRoute() + { + return $this->data['route']; + } + + public function getIdentifier() + { + return $this->data['identifier']; + } + + /** + * Gets the route parameters. + * + * The _route_params request attributes is automatically set by the RouterListener. + * + * @return array The parameters + */ + public function getRouteParams() + { + return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params']->getValue() : array(); + } + + /** + * Gets the parsed controller. + * + * @return array|string The controller as a string or array of data + * with keys 'class', 'method', 'file' and 'line' + */ + public function getController() + { + return $this->data['controller']; + } + + /** + * Gets the previous request attributes. + * + * @return array|bool A legacy array of data from the previous redirection response + * or false otherwise + */ + public function getRedirect() + { + return isset($this->data['redirect']) ? $this->data['redirect'] : false; + } + + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest() || !$event->getRequest()->hasSession() || !$event->getRequest()->getSession()->isStarted()) { + return; + } + + if ($event->getRequest()->getSession()->has('sf_redirect')) { + $event->getRequest()->attributes->set('_redirected', true); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::CONTROLLER => 'onKernelController', + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'request'; + } + + /** + * Parse a controller. + * + * @param mixed $controller The controller to parse + * + * @return array|string An array of controller data or a simple string + */ + protected function parseController($controller) + { + if (is_string($controller) && false !== strpos($controller, '::')) { + $controller = explode('::', $controller); + } + + if (is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + + return array( + 'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } catch (\ReflectionException $e) { + if (is_callable($controller)) { + // using __call or __callStatic + return array( + 'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => 'n/a', + 'line' => 'n/a', + ); + } + } + } + + if ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + + return array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } + + if (is_object($controller)) { + $r = new \ReflectionClass($controller); + + return array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } + + return is_string($controller) ? $controller : 'n/a'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php new file mode 100644 index 00000000..76d96234 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; + +/** + * RouterDataCollector. + * + * @author Fabien Potencier + */ +class RouterDataCollector extends DataCollector +{ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + + $this->data = array( + 'redirect' => false, + 'url' => null, + 'route' => null, + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if ($response instanceof RedirectResponse) { + $this->data['redirect'] = true; + $this->data['url'] = $response->getTargetUrl(); + + if ($this->controllers->contains($request)) { + $this->data['route'] = $this->guessRoute($request, $this->controllers[$request]); + } + } + + unset($this->controllers[$request]); + } + + protected function guessRoute(Request $request, $controller) + { + return 'n/a'; + } + + /** + * Remembers the controller associated to each request. + * + * @param FilterControllerEvent $event The filter controller event + */ + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + /** + * @return bool Whether this request will result in a redirect + */ + public function getRedirect() + { + return $this->data['redirect']; + } + + /** + * @return string|null The target URL + */ + public function getTargetUrl() + { + return $this->data['url']; + } + + /** + * @return string|null The target route + */ + public function getTargetRoute() + { + return $this->data['route']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'router'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php new file mode 100644 index 00000000..2d39156e --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * TimeDataCollector. + * + * @author Fabien Potencier + */ +class TimeDataCollector extends DataCollector implements LateDataCollectorInterface +{ + protected $kernel; + protected $stopwatch; + + public function __construct(KernelInterface $kernel = null, $stopwatch = null) + { + $this->kernel = $kernel; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $this->kernel) { + $startTime = $this->kernel->getStartTime(); + } else { + $startTime = $request->server->get('REQUEST_TIME_FLOAT'); + } + + $this->data = array( + 'token' => $response->headers->get('X-Debug-Token'), + 'start_time' => $startTime * 1000, + 'events' => array(), + ); + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + if (null !== $this->stopwatch && isset($this->data['token'])) { + $this->setEvents($this->stopwatch->getSectionEvents($this->data['token'])); + } + unset($this->data['token']); + } + + /** + * Sets the request events. + * + * @param array $events The request events + */ + public function setEvents(array $events) + { + foreach ($events as $event) { + $event->ensureStopped(); + } + + $this->data['events'] = $events; + } + + /** + * Gets the request events. + * + * @return array The request events + */ + public function getEvents() + { + return $this->data['events']; + } + + /** + * Gets the request elapsed time. + * + * @return float The elapsed time + */ + public function getDuration() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + $lastEvent = $this->data['events']['__section__']; + + return $lastEvent->getOrigin() + $lastEvent->getDuration() - $this->getStartTime(); + } + + /** + * Gets the initialization time. + * + * This is the time spent until the beginning of the request handling. + * + * @return float The elapsed time + */ + public function getInitTime() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + return $this->data['events']['__section__']->getOrigin() - $this->getStartTime(); + } + + /** + * Gets the request time. + * + * @return int The time + */ + public function getStartTime() + { + return $this->data['start_time']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'time'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php b/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php new file mode 100644 index 00000000..f1e48311 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.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\HttpKernel\DataCollector\Util; + +@trigger_error('The '.__NAMESPACE__.'\ValueExporter class is deprecated since version 3.2 and will be removed in 4.0. Use the VarDumper component instead.', E_USER_DEPRECATED); + +/** + * @author Bernhard Schussek + * + * @deprecated since version 3.2, to be removed in 4.0. Use the VarDumper component instead. + */ +class ValueExporter +{ + /** + * Converts a PHP value to a string. + * + * @param mixed $value The PHP value + * @param int $depth only for internal usage + * @param bool $deep only for internal usage + * + * @return string The string representation of the given value + */ + public function exportValue($value, $depth = 1, $deep = false) + { + if ($value instanceof \__PHP_Incomplete_Class) { + return sprintf('__PHP_Incomplete_Class(%s)', $this->getClassNameFromIncomplete($value)); + } + + if (is_object($value)) { + if ($value instanceof \DateTimeInterface) { + return sprintf('Object(%s) - %s', get_class($value), $value->format(\DateTime::ATOM)); + } + + return sprintf('Object(%s)', get_class($value)); + } + + if (is_array($value)) { + if (empty($value)) { + return '[]'; + } + + $indent = str_repeat(' ', $depth); + + $a = array(); + foreach ($value as $k => $v) { + if (is_array($v)) { + $deep = true; + } + $a[] = sprintf('%s => %s', $k, $this->exportValue($v, $depth + 1, $deep)); + } + + if ($deep) { + return sprintf("[\n%s%s\n%s]", $indent, implode(sprintf(", \n%s", $indent), $a), str_repeat(' ', $depth - 1)); + } + + $s = sprintf('[%s]', implode(', ', $a)); + + if (80 > strlen($s)) { + return $s; + } + + return sprintf("[\n%s%s\n]", $indent, implode(sprintf(",\n%s", $indent), $a)); + } + + if (is_resource($value)) { + return sprintf('Resource(%s#%d)', get_resource_type($value), $value); + } + + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + return (string) $value; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php b/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php new file mode 100644 index 00000000..c340d9b6 --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/FileLinkFormatter.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Formats debug file links. + * + * @author Jérémy Romey + */ +class FileLinkFormatter implements \Serializable +{ + private $fileLinkFormat; + private $requestStack; + private $baseDir; + private $urlFormat; + + public function __construct($fileLinkFormat = null, RequestStack $requestStack = null, $baseDir = null, $urlFormat = null) + { + $fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + if ($fileLinkFormat && !is_array($fileLinkFormat)) { + $i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: strlen($f); + $fileLinkFormat = array(substr($f, 0, $i)) + preg_split('/&([^>]++)>/', substr($f, $i), -1, PREG_SPLIT_DELIM_CAPTURE); + } + + $this->fileLinkFormat = $fileLinkFormat; + $this->requestStack = $requestStack; + $this->baseDir = $baseDir; + $this->urlFormat = $urlFormat; + } + + public function format($file, $line) + { + if ($fmt = $this->getFileLinkFormat()) { + for ($i = 1; isset($fmt[$i]); ++$i) { + if (0 === strpos($file, $k = $fmt[$i++])) { + $file = substr_replace($file, $fmt[$i], 0, strlen($k)); + break; + } + } + + return strtr($fmt[0], array('%f' => $file, '%l' => $line)); + } + + return false; + } + + public function serialize() + { + return serialize($this->getFileLinkFormat()); + } + + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $this->fileLinkFormat = unserialize($serialized, array('allowed_classes' => false)); + } else { + $this->fileLinkFormat = unserialize($serialized); + } + } + + private function getFileLinkFormat() + { + if ($this->fileLinkFormat) { + return $this->fileLinkFormat; + } + if ($this->requestStack && $this->baseDir && $this->urlFormat) { + $request = $this->requestStack->getMasterRequest(); + if ($request instanceof Request) { + return array( + $request->getSchemeAndHttpHost().$request->getBaseUrl().$this->urlFormat, + $this->baseDir.DIRECTORY_SEPARATOR, '', + ); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php new file mode 100644 index 00000000..fbc49dff --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\Event; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher extends BaseTraceableEventDispatcher +{ + /** + * {@inheritdoc} + */ + protected function preDispatch($eventName, Event $event) + { + switch ($eventName) { + case KernelEvents::REQUEST: + $this->stopwatch->openSection(); + break; + case KernelEvents::VIEW: + case KernelEvents::RESPONSE: + // stop only if a controller has been executed + if ($this->stopwatch->isStarted('controller')) { + $this->stopwatch->stop('controller'); + } + break; + case KernelEvents::TERMINATE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + // There is a very special case when using built-in AppCache class as kernel wrapper, in the case + // of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A]. + // In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID + // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception + // which must be caught. + try { + $this->stopwatch->openSection($token); + } catch (\LogicException $e) { + } + break; + } + } + + /** + * {@inheritdoc} + */ + protected function postDispatch($eventName, Event $event) + { + switch ($eventName) { + case KernelEvents::CONTROLLER_ARGUMENTS: + $this->stopwatch->start('controller', 'section'); + break; + case KernelEvents::RESPONSE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + $this->stopwatch->stopSection($token); + break; + case KernelEvents::TERMINATE: + // In the special case described in the `preDispatch` method above, the `$token` section + // does not exist, then closing it throws an exception which must be caught. + $token = $event->getResponse()->headers->get('X-Debug-Token'); + try { + $this->stopwatch->stopSection($token); + } catch (\LogicException $e) { + } + break; + } + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php b/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php new file mode 100644 index 00000000..9235ae4e --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Composer\Autoload\ClassLoader; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\HttpKernel\Kernel; + +/** + * Sets the classes to compile in the cache for the container. + * + * @author Fabien Potencier + */ +class AddAnnotatedClassesToCachePass implements CompilerPassInterface +{ + private $kernel; + + public function __construct(Kernel $kernel) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $classes = array(); + $annotatedClasses = array(); + foreach ($container->getExtensions() as $extension) { + if ($extension instanceof Extension) { + if (\PHP_VERSION_ID < 70000) { + $classes = array_merge($classes, $extension->getClassesToCompile()); + } + $annotatedClasses = array_merge($annotatedClasses, $extension->getAnnotatedClassesToCompile()); + } + } + + $existingClasses = $this->getClassesInComposerClassMaps(); + + if (\PHP_VERSION_ID < 70000) { + $classes = $container->getParameterBag()->resolveValue($classes); + $this->kernel->setClassCache($this->expandClasses($classes, $existingClasses)); + } + $annotatedClasses = $container->getParameterBag()->resolveValue($annotatedClasses); + $this->kernel->setAnnotatedClassCache($this->expandClasses($annotatedClasses, $existingClasses)); + } + + /** + * Expands the given class patterns using a list of existing classes. + * + * @param array $patterns The class patterns to expand + * @param array $classes The existing classes to match against the patterns + * + * @return array A list of classes derivated from the patterns + */ + private function expandClasses(array $patterns, array $classes) + { + $expanded = array(); + + // Explicit classes declared in the patterns are returned directly + foreach ($patterns as $key => $pattern) { + if (substr($pattern, -1) !== '\\' && false === strpos($pattern, '*')) { + unset($patterns[$key]); + $expanded[] = ltrim($pattern, '\\'); + } + } + + // Match patterns with the classes list + $regexps = $this->patternsToRegexps($patterns); + + foreach ($classes as $class) { + $class = ltrim($class, '\\'); + + if ($this->matchAnyRegexps($class, $regexps)) { + $expanded[] = $class; + } + } + + return array_unique($expanded); + } + + private function getClassesInComposerClassMaps() + { + $classes = array(); + + foreach (spl_autoload_functions() as $function) { + if (!is_array($function)) { + continue; + } + + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + } + + if (is_array($function) && $function[0] instanceof ClassLoader) { + $classes += array_filter($function[0]->getClassMap()); + } + } + + return array_keys($classes); + } + + private function patternsToRegexps($patterns) + { + $regexps = array(); + + foreach ($patterns as $pattern) { + // Escape user input + $regex = preg_quote(ltrim($pattern, '\\')); + + // Wildcards * and ** + $regex = strtr($regex, array('\\*\\*' => '.*?', '\\*' => '[^\\\\]*?')); + + // If this class does not end by a slash, anchor the end + if (substr($regex, -1) !== '\\') { + $regex .= '$'; + } + + $regexps[] = '{^\\\\'.$regex.'}'; + } + + return $regexps; + } + + private function matchAnyRegexps($class, $regexps) + { + $blacklisted = false !== strpos($class, 'Test'); + + foreach ($regexps as $regex) { + if ($blacklisted && false === strpos($regex, 'Test')) { + continue; + } + + if (preg_match($regex, '\\'.$class)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php new file mode 100644 index 00000000..4ffa1751 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +@trigger_error('The '.__NAMESPACE__.'\AddClassesToCachePass class is deprecated since version 3.3 and will be removed in 4.0.', E_USER_DEPRECATED); + +/** + * Sets the classes to compile in the cache for the container. + * + * @author Fabien Potencier + * + * @deprecated since version 3.3, to be removed in 4.0. + */ +class AddClassesToCachePass extends AddAnnotatedClassesToCachePass +{ +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php new file mode 100644 index 00000000..c7eca306 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This extension sub-class provides first-class integration with the + * Config/Definition Component. + * + * You can use this as base class if + * + * a) you use the Config/Definition component for configuration, + * b) your configuration class is named "Configuration", and + * c) the configuration class resides in the DependencyInjection sub-folder. + * + * @author Johannes M. Schmitt + */ +abstract class ConfigurableExtension extends Extension +{ + /** + * {@inheritdoc} + */ + final public function load(array $configs, ContainerBuilder $container) + { + $this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container); + } + + /** + * Configures the passed container according to the merged configuration. + * + * @param array $mergedConfig + * @param ContainerBuilder $container + */ + abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container); +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php new file mode 100644 index 00000000..343e217b --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Gathers and configures the argument value resolvers. + * + * @author Iltar van der Berg + */ +class ControllerArgumentValueResolverPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + private $argumentResolverService; + private $argumentValueResolverTag; + + public function __construct($argumentResolverService = 'argument_resolver', $argumentValueResolverTag = 'controller.argument_value_resolver') + { + $this->argumentResolverService = $argumentResolverService; + $this->argumentValueResolverTag = $argumentValueResolverTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->argumentResolverService)) { + return; + } + + $container + ->getDefinition($this->argumentResolverService) + ->replaceArgument(1, new IteratorArgument($this->findAndSortTaggedServices($this->argumentValueResolverTag, $container))) + ; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/Extension.php b/vendor/symfony/http-kernel/DependencyInjection/Extension.php new file mode 100644 index 00000000..f6449f4e --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/Extension.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension; + +/** + * Allow adding classes to the class cache. + * + * @author Fabien Potencier + */ +abstract class Extension extends BaseExtension +{ + private $classes = array(); + private $annotatedClasses = array(); + + /** + * Gets the classes to cache. + * + * @return array An array of classes + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function getClassesToCompile() + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + return $this->classes; + } + + /** + * Gets the annotated classes to cache. + * + * @return array An array of classes + */ + public function getAnnotatedClassesToCompile() + { + return $this->annotatedClasses; + } + + /** + * Adds classes to the class cache. + * + * @param array $classes An array of class patterns + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function addClassesToCompile(array $classes) + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + $this->classes = array_merge($this->classes, $classes); + } + + /** + * Adds annotated classes to the class cache. + * + * @param array $annotatedClasses An array of class patterns + */ + public function addAnnotatedClassesToCompile(array $annotatedClasses) + { + $this->annotatedClasses = array_merge($this->annotatedClasses, $annotatedClasses); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php new file mode 100644 index 00000000..ac52c873 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.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\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +/** + * Adds services tagged kernel.fragment_renderer as HTTP content rendering strategies. + * + * @author Fabien Potencier + */ +class FragmentRendererPass implements CompilerPassInterface +{ + private $handlerService; + private $rendererTag; + + /** + * @param string $handlerService Service name of the fragment handler in the container + * @param string $rendererTag Tag name used for fragments + */ + public function __construct($handlerService = 'fragment.handler', $rendererTag = 'kernel.fragment_renderer') + { + $this->handlerService = $handlerService; + $this->rendererTag = $rendererTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->handlerService)) { + return; + } + + $definition = $container->getDefinition($this->handlerService); + $renderers = array(); + foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $id => $tags) { + $def = $container->getDefinition($id); + $class = $container->getParameterBag()->resolveValue($def->getClass()); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + if (!$r->isSubclassOf(FragmentRendererInterface::class)) { + throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, FragmentRendererInterface::class)); + } + + foreach ($tags as $tag) { + $renderers[$tag['alias']] = new Reference($id); + } + } + + $definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers)); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php new file mode 100644 index 00000000..d6f4dab1 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.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\HttpKernel\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; + +/** + * Lazily loads fragment renderers from the dependency injection container. + * + * @author Fabien Potencier + */ +class LazyLoadingFragmentHandler extends FragmentHandler +{ + private $container; + /** + * @deprecated since version 3.3, to be removed in 4.0 + */ + private $rendererIds = array(); + private $initialized = array(); + + /** + * Constructor. + * + * @param ContainerInterface $container A container + * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests + * @param bool $debug Whether the debug mode is enabled or not + */ + public function __construct(ContainerInterface $container, RequestStack $requestStack, $debug = false) + { + $this->container = $container; + + parent::__construct($requestStack, array(), $debug); + } + + /** + * Adds a service as a fragment renderer. + * + * @param string $name The service name + * @param string $renderer The render service id + * + * @deprecated since version 3.3, to be removed in 4.0 + */ + public function addRendererService($name, $renderer) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + + $this->rendererIds[$name] = $renderer; + } + + /** + * {@inheritdoc} + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + // BC 3.x, to be removed in 4.0 + if (isset($this->rendererIds[$renderer])) { + $this->addRenderer($this->container->get($this->rendererIds[$renderer])); + unset($this->rendererIds[$renderer]); + + return parent::render($uri, $renderer, $options); + } + + if (!isset($this->initialized[$renderer]) && $this->container->has($renderer)) { + $this->addRenderer($this->container->get($renderer)); + $this->initialized[$renderer] = true; + } + + return parent::render($uri, $renderer, $options); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php new file mode 100644 index 00000000..dcd73828 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass as BaseMergeExtensionConfigurationPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Ensures certain extensions are always loaded. + * + * @author Kris Wallsmith + */ +class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass +{ + private $extensions; + + public function __construct(array $extensions) + { + $this->extensions = $extensions; + } + + public function process(ContainerBuilder $container) + { + foreach ($this->extensions as $extension) { + if (!count($container->getExtensionConfig($extension))) { + $container->loadFromExtension($extension, array()); + } + } + + parent::process($container); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php new file mode 100644 index 00000000..222185f5 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; + +/** + * Creates the service-locators required by ServiceValueResolver. + * + * @author Nicolas Grekas + */ +class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface +{ + private $resolverServiceId; + private $controllerTag; + + public function __construct($resolverServiceId = 'argument_resolver.service', $controllerTag = 'controller.service_arguments') + { + $this->resolverServiceId = $resolverServiceId; + $this->controllerTag = $controllerTag; + } + + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition($this->resolverServiceId)) { + return; + } + + $parameterBag = $container->getParameterBag(); + $controllers = array(); + + foreach ($container->findTaggedServiceIds($this->controllerTag, true) as $id => $tags) { + $def = $container->getDefinition($id); + $class = $def->getClass(); + $autowire = $def->isAutowired(); + + // resolve service class, taking parent definitions into account + while (!$class && $def instanceof ChildDefinition) { + $def = $container->findDefinition($def->getParent()); + $class = $def->getClass(); + } + $class = $parameterBag->resolveValue($class); + + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + $isContainerAware = $r->implementsInterface(ContainerAwareInterface::class) || is_subclass_of($class, AbstractController::class); + + // get regular public methods + $methods = array(); + $arguments = array(); + foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $r) { + if ('setContainer' === $r->name && $isContainerAware) { + continue; + } + if (!$r->isConstructor() && !$r->isDestructor() && !$r->isAbstract()) { + $methods[strtolower($r->name)] = array($r, $r->getParameters()); + } + } + + // validate and collect explicit per-actions and per-arguments service references + foreach ($tags as $attributes) { + if (!isset($attributes['action']) && !isset($attributes['argument']) && !isset($attributes['id'])) { + $autowire = true; + continue; + } + foreach (array('action', 'argument', 'id') as $k) { + if (!isset($attributes[$k][0])) { + throw new InvalidArgumentException(sprintf('Missing "%s" attribute on tag "%s" %s for service "%s".', $k, $this->controllerTag, json_encode($attributes, JSON_UNESCAPED_UNICODE), $id)); + } + } + if (!isset($methods[$action = strtolower($attributes['action'])])) { + throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "%s" for service "%s": no public "%s()" method found on class "%s".', $this->controllerTag, $id, $attributes['action'], $class)); + } + list($r, $parameters) = $methods[$action]; + $found = false; + + foreach ($parameters as $p) { + if ($attributes['argument'] === $p->name) { + if (!isset($arguments[$r->name][$p->name])) { + $arguments[$r->name][$p->name] = $attributes['id']; + } + $found = true; + break; + } + } + + if (!$found) { + throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": method "%s()" has no "%s" argument on class "%s".', $this->controllerTag, $id, $r->name, $attributes['argument'], $class)); + } + } + + foreach ($methods as list($r, $parameters)) { + /** @var \ReflectionMethod $r */ + + // create a per-method map of argument-names to service/type-references + $args = array(); + foreach ($parameters as $p) { + /** @var \ReflectionParameter $p */ + $type = $target = ProxyHelper::getTypeHint($r, $p, true); + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + + if (isset($arguments[$r->name][$p->name])) { + $target = $arguments[$r->name][$p->name]; + if ('?' !== $target[0]) { + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + } elseif ('' === $target = (string) substr($target, 1)) { + throw new InvalidArgumentException(sprintf('A "%s" tag must have non-empty "id" attributes for service "%s".', $this->controllerTag, $id)); + } elseif ($p->allowsNull() && !$p->isOptional()) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } + } elseif (!$type || !$autowire) { + continue; + } + + if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) { + $message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type); + + // see if the type-hint lives in the same namespace as the controller + if (0 === strncmp($type, $class, strrpos($class, '\\'))) { + $message .= ' Did you forget to add a use statement?'; + } + + throw new InvalidArgumentException($message); + } + + $args[$p->name] = $type ? new TypedReference($target, $type, $r->class, $invalidBehavior) : new Reference($target, $invalidBehavior); + } + // register the maps as a per-method service-locators + if ($args) { + $controllers[$id.':'.$r->name] = ServiceLocatorTagPass::register($container, $args); + } + } + } + + $container->getDefinition($this->resolverServiceId) + ->replaceArgument(0, ServiceLocatorTagPass::register($container, $controllers)); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php new file mode 100644 index 00000000..440b0d00 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Removes empty service-locators registered for ServiceValueResolver. + * + * @author Nicolas Grekas + */ +class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface +{ + private $resolverServiceId; + + public function __construct($resolverServiceId = 'argument_resolver.service') + { + $this->resolverServiceId = $resolverServiceId; + } + + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition($this->resolverServiceId)) { + return; + } + + $serviceResolver = $container->getDefinition($this->resolverServiceId); + $controllerLocator = $container->getDefinition((string) $serviceResolver->getArgument(0)); + $controllers = $controllerLocator->getArgument(0); + + foreach ($controllers as $controller => $argumentRef) { + $argumentLocator = $container->getDefinition((string) $argumentRef->getValues()[0]); + + if (!$argumentLocator->getArgument(0)) { + // remove empty argument locators + $reason = sprintf('Removing service-argument resolver for controller "%s": no corresponding services exist for the referenced types.', $controller); + } else { + // any methods listed for call-at-instantiation cannot be actions + $reason = false; + $action = substr(strrchr($controller, ':'), 1); + $id = substr($controller, 0, -1 - strlen($action)); + $controllerDef = $container->getDefinition($id); + foreach ($controllerDef->getMethodCalls() as list($method, $args)) { + if (0 === strcasecmp($action, $method)) { + $reason = sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $id); + break; + } + } + if (!$reason) { + if ($controllerDef->getClass() === $id) { + $controllers[$id.'::'.$action] = $argumentRef; + } + if ('__invoke' === $action) { + $controllers[$id] = $argumentRef; + } + continue; + } + } + + unset($controllers[$controller]); + $container->log($this, $reason); + } + + $controllerLocator->replaceArgument(0, $controllers); + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php b/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php new file mode 100644 index 00000000..1dc784ed --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows filtering of controller arguments. + * + * You can call getController() to retrieve the controller and getArguments + * to retrieve the current arguments. With setArguments() you can replace + * arguments that are used to call the controller. + * + * Arguments set in the event must be compatible with the signature of the + * controller. + * + * @author Christophe Coevoet + */ +class FilterControllerArgumentsEvent extends FilterControllerEvent +{ + private $arguments; + + public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, $requestType) + { + parent::__construct($kernel, $controller, $request, $requestType); + + $this->arguments = $arguments; + } + + /** + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * @param array $arguments + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterControllerEvent.php b/vendor/symfony/http-kernel/Event/FilterControllerEvent.php new file mode 100644 index 00000000..e0d46aa4 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterControllerEvent.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows filtering of a controller callable. + * + * You can call getController() to retrieve the current controller. With + * setController() you can set a new controller that is used in the processing + * of the request. + * + * Controllers should be callables. + * + * @author Bernhard Schussek + */ +class FilterControllerEvent extends KernelEvent +{ + /** + * The current controller. + */ + private $controller; + + public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, $requestType) + { + parent::__construct($kernel, $request, $requestType); + + $this->setController($controller); + } + + /** + * Returns the current controller. + * + * @return callable + */ + public function getController() + { + return $this->controller; + } + + /** + * Sets a new controller. + * + * @param callable $controller + */ + public function setController(callable $controller) + { + $this->controller = $controller; + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterResponseEvent.php b/vendor/symfony/http-kernel/Event/FilterResponseEvent.php new file mode 100644 index 00000000..ed816a9d --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterResponseEvent.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\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to filter a Response object. + * + * You can call getResponse() to retrieve the current response. With + * setResponse() you can set a new response that will be returned to the + * browser. + * + * @author Bernhard Schussek + */ +class FilterResponseEvent extends KernelEvent +{ + /** + * The current response object. + * + * @var Response + */ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, Response $response) + { + parent::__construct($kernel, $request, $requestType); + + $this->setResponse($response); + } + + /** + * Returns the current response object. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a new response object. + * + * @param Response $response + */ + public function setResponse(Response $response) + { + $this->response = $response; + } +} diff --git a/vendor/symfony/http-kernel/Event/FinishRequestEvent.php b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php new file mode 100644 index 00000000..ee724843 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +/** + * Triggered whenever a request is fully processed. + * + * @author Benjamin Eberlei + */ +class FinishRequestEvent extends KernelEvent +{ +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseEvent.php b/vendor/symfony/http-kernel/Event/GetResponseEvent.php new file mode 100644 index 00000000..4c96a4b7 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseEvent.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to create a response for a request. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +class GetResponseEvent extends KernelEvent +{ + /** + * The response object. + * + * @var Response + */ + private $response; + + /** + * Returns the response object. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a response and stops event propagation. + * + * @param Response $response + */ + public function setResponse(Response $response) + { + $this->response = $response; + + $this->stopPropagation(); + } + + /** + * Returns whether a response was set. + * + * @return bool Whether a response was set + */ + public function hasResponse() + { + return null !== $this->response; + } +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php b/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php new file mode 100644 index 00000000..f70ce09e --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows to create a response for the return value of a controller. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +class GetResponseForControllerResultEvent extends GetResponseEvent +{ + /** + * The return value of the controller. + * + * @var mixed + */ + private $controllerResult; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, $controllerResult) + { + parent::__construct($kernel, $request, $requestType); + + $this->controllerResult = $controllerResult; + } + + /** + * Returns the return value of the controller. + * + * @return mixed The controller return value + */ + public function getControllerResult() + { + return $this->controllerResult; + } + + /** + * Assigns the return value of the controller. + * + * @param mixed $controllerResult The controller return value + */ + public function setControllerResult($controllerResult) + { + $this->controllerResult = $controllerResult; + } +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php b/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php new file mode 100644 index 00000000..751b7451 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows to create a response for a thrown exception. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * You can also call setException() to replace the thrown exception. This + * exception will be thrown if no response is set during processing of this + * event. + * + * @author Bernhard Schussek + */ +class GetResponseForExceptionEvent extends GetResponseEvent +{ + /** + * The exception object. + * + * @var \Exception + */ + private $exception; + + /** + * @var bool + */ + private $allowCustomResponseCode = false; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e) + { + parent::__construct($kernel, $request, $requestType); + + $this->setException($e); + } + + /** + * Returns the thrown exception. + * + * @return \Exception The thrown exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + * + * @param \Exception $exception The thrown exception + */ + public function setException(\Exception $exception) + { + $this->exception = $exception; + } + + /** + * Mark the event as allowing a custom response code. + */ + public function allowCustomResponseCode() + { + $this->allowCustomResponseCode = true; + } + + /** + * Returns true if the event allows a custom response code. + * + * @return bool + */ + public function isAllowingCustomResponseCode() + { + return $this->allowCustomResponseCode; + } +} diff --git a/vendor/symfony/http-kernel/Event/KernelEvent.php b/vendor/symfony/http-kernel/Event/KernelEvent.php new file mode 100644 index 00000000..2043a017 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/KernelEvent.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\Event; + +/** + * Base class for events thrown in the HttpKernel component. + * + * @author Bernhard Schussek + */ +class KernelEvent extends Event +{ + /** + * The kernel in which this event was thrown. + * + * @var HttpKernelInterface + */ + private $kernel; + + /** + * The request the kernel is currently processing. + * + * @var Request + */ + private $request; + + /** + * The request type the kernel is currently processing. One of + * HttpKernelInterface::MASTER_REQUEST and HttpKernelInterface::SUB_REQUEST. + * + * @var int + */ + private $requestType; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType) + { + $this->kernel = $kernel; + $this->request = $request; + $this->requestType = $requestType; + } + + /** + * Returns the kernel in which this event was thrown. + * + * @return HttpKernelInterface + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Returns the request the kernel is currently processing. + * + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the request type the kernel is currently processing. + * + * @return int One of HttpKernelInterface::MASTER_REQUEST and + * HttpKernelInterface::SUB_REQUEST + */ + public function getRequestType() + { + return $this->requestType; + } + + /** + * Checks if this is a master request. + * + * @return bool True if the request is a master request + */ + public function isMasterRequest() + { + return HttpKernelInterface::MASTER_REQUEST === $this->requestType; + } +} diff --git a/vendor/symfony/http-kernel/Event/PostResponseEvent.php b/vendor/symfony/http-kernel/Event/PostResponseEvent.php new file mode 100644 index 00000000..2406fddb --- /dev/null +++ b/vendor/symfony/http-kernel/Event/PostResponseEvent.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to execute logic after a response was sent. + * + * Since it's only triggered on master requests, the `getRequestType()` method + * will always return the value of `HttpKernelInterface::MASTER_REQUEST`. + * + * @author Jordi Boggiano + */ +class PostResponseEvent extends KernelEvent +{ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) + { + parent::__construct($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $this->response = $response; + } + + /** + * Returns the response for which this event was thrown. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php b/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php new file mode 100644 index 00000000..7a6c2073 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AbstractSessionListener.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Sets the session in the request. + * + * @author Johannes M. Schmitt + */ +abstract class AbstractSessionListener implements EventSubscriberInterface +{ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $request = $event->getRequest(); + $session = $this->getSession(); + if (null === $session || $request->hasSession()) { + return; + } + + $request->setSession($session); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 128), + ); + } + + /** + * Gets the session object. + * + * @return SessionInterface|null A SessionInterface instance or null if no session is available + */ + abstract protected function getSession(); +} diff --git a/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php b/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.php new file mode 100644 index 00000000..eb6e66c0 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AbstractTestSessionListener.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\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * TestSessionListener. + * + * Saves session in test environment. + * + * @author Bulat Shakirzyanov + * @author Fabien Potencier + */ +abstract class AbstractTestSessionListener implements EventSubscriberInterface +{ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + // bootstrap the session + $session = $this->getSession(); + if (!$session) { + return; + } + + $cookies = $event->getRequest()->cookies; + + if ($cookies->has($session->getName())) { + $session->setId($cookies->get($session->getName())); + } + } + + /** + * Checks if session was initialized and saves if current request is master + * Runs on 'kernel.response' in test environment. + * + * @param FilterResponseEvent $event + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $session = $event->getRequest()->getSession(); + if ($session && $session->isStarted()) { + $session->save(); + $params = session_get_cookie_params(); + $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 192), + KernelEvents::RESPONSE => array('onKernelResponse', -128), + ); + } + + /** + * Gets the session object. + * + * @return SessionInterface|null A SessionInterface instance or null if no session is available + */ + abstract protected function getSession(); +} diff --git a/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php new file mode 100644 index 00000000..280844c1 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; + +/** + * Adds configured formats to each request. + * + * @author Gildas Quemener + */ +class AddRequestFormatsListener implements EventSubscriberInterface +{ + /** + * @var array + */ + protected $formats; + + /** + * @param array $formats + */ + public function __construct(array $formats) + { + $this->formats = $formats; + } + + /** + * Adds request formats. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + foreach ($this->formats as $format => $mimeTypes) { + $event->getRequest()->setFormat($format, $mimeTypes); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array(KernelEvents::REQUEST => array('onKernelRequest', 1)); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php new file mode 100644 index 00000000..29e1725a --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Output\ConsoleOutputInterface; + +/** + * Configures errors and exceptions handlers. + * + * @author Nicolas Grekas + */ +class DebugHandlersListener implements EventSubscriberInterface +{ + private $exceptionHandler; + private $logger; + private $levels; + private $throwAt; + private $scream; + private $fileLinkFormat; + private $scope; + private $firstCall = true; + + /** + * @param callable|null $exceptionHandler A handler that will be called on Exception + * @param LoggerInterface|null $logger A PSR-3 logger + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param string|array $fileLinkFormat The format for links to source files + * @param bool $scope Enables/disables scoping mode + */ + public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, $throwAt = E_ALL, $scream = true, $fileLinkFormat = null, $scope = true) + { + $this->exceptionHandler = $exceptionHandler; + $this->logger = $logger; + $this->levels = null === $levels ? E_ALL : $levels; + $this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? E_ALL : null)); + $this->scream = (bool) $scream; + $this->fileLinkFormat = $fileLinkFormat; + $this->scope = (bool) $scope; + } + + /** + * Configures the error handler. + * + * @param Event|null $event The triggering event + */ + public function configure(Event $event = null) + { + if (!$this->firstCall) { + return; + } + $this->firstCall = false; + if ($this->logger || null !== $this->throwAt) { + $handler = set_error_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler instanceof ErrorHandler) { + if ($this->logger) { + $handler->setDefaultLogger($this->logger, $this->levels); + if (is_array($this->levels)) { + $levels = 0; + foreach ($this->levels as $type => $log) { + $levels |= $type; + } + } else { + $levels = $this->levels; + } + if ($this->scream) { + $handler->screamAt($levels); + } + if ($this->scope) { + $handler->scopeAt($this->levels); + } else { + $handler->scopeAt(0, true); + } + $this->logger = $this->levels = null; + } + if (null !== $this->throwAt) { + $handler->throwAt($this->throwAt, true); + } + } + } + if (!$this->exceptionHandler) { + if ($event instanceof KernelEvent) { + if (method_exists($event->getKernel(), 'terminateWithException')) { + $this->exceptionHandler = array($event->getKernel(), 'terminateWithException'); + } + } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) { + $output = $event->getOutput(); + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->exceptionHandler = function ($e) use ($app, $output) { + $app->renderException($e, $output); + }; + } + } + if ($this->exceptionHandler) { + $handler = set_exception_handler('var_dump'); + $handler = is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + if ($handler instanceof ErrorHandler) { + $h = $handler->setExceptionHandler('var_dump') ?: $this->exceptionHandler; + $handler->setExceptionHandler($h); + $handler = is_array($h) ? $h[0] : null; + } + if ($handler instanceof ExceptionHandler) { + $handler->setHandler($this->exceptionHandler); + if (null !== $this->fileLinkFormat) { + $handler->setFileLinkFormat($this->fileLinkFormat); + } + } + $this->exceptionHandler = null; + } + } + + public static function getSubscribedEvents() + { + $events = array(KernelEvents::REQUEST => array('configure', 2048)); + + if ('cli' === PHP_SAPI && defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) { + $events[ConsoleEvents::COMMAND] = array('configure', 2048); + } + + return $events; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DumpListener.php b/vendor/symfony/http-kernel/EventListener/DumpListener.php new file mode 100644 index 00000000..88acef31 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DumpListener.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\VarDumper; + +/** + * Configures dump() handler. + * + * @author Nicolas Grekas + */ +class DumpListener implements EventSubscriberInterface +{ + private $cloner; + private $dumper; + + /** + * @param ClonerInterface $cloner Cloner service + * @param DataDumperInterface $dumper Dumper service + */ + public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper) + { + $this->cloner = $cloner; + $this->dumper = $dumper; + } + + public function configure() + { + $cloner = $this->cloner; + $dumper = $this->dumper; + + VarDumper::setHandler(function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }); + } + + public static function getSubscribedEvents() + { + if (!class_exists(ConsoleEvents::class)) { + return array(); + } + + // Register early to have a working dump() as early as possible + return array(ConsoleEvents::COMMAND => array('configure', 1024)); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ExceptionListener.php b/vendor/symfony/http-kernel/EventListener/ExceptionListener.php new file mode 100644 index 00000000..cf3a2f0a --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ExceptionListener.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ExceptionListener. + * + * @author Fabien Potencier + */ +class ExceptionListener implements EventSubscriberInterface +{ + protected $controller; + protected $logger; + + public function __construct($controller, LoggerInterface $logger = null) + { + $this->controller = $controller; + $this->logger = $logger; + } + + public function onKernelException(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + $request = $event->getRequest(); + + $this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine())); + + $request = $this->duplicateRequest($exception, $request); + + try { + $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine())); + + $wrapper = $e; + + while ($prev = $wrapper->getPrevious()) { + if ($exception === $wrapper = $prev) { + throw $e; + } + } + + $prev = new \ReflectionProperty('Exception', 'previous'); + $prev->setAccessible(true); + $prev->setValue($wrapper, $exception); + + throw $e; + } + + $event->setResponse($response); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => array('onKernelException', -128), + ); + } + + /** + * Logs an exception. + * + * @param \Exception $exception The \Exception instance + * @param string $message The error message to log + */ + protected function logException(\Exception $exception, $message) + { + if (null !== $this->logger) { + if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { + $this->logger->critical($message, array('exception' => $exception)); + } else { + $this->logger->error($message, array('exception' => $exception)); + } + } + } + + /** + * Clones the request for the exception. + * + * @param \Exception $exception The thrown exception + * @param Request $request The original request + * + * @return Request $request The cloned request + */ + protected function duplicateRequest(\Exception $exception, Request $request) + { + $attributes = array( + '_controller' => $this->controller, + 'exception' => FlattenException::create($exception), + 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, + ); + $request = $request->duplicate(null, null, $attributes); + $request->setMethod('GET'); + + return $request; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/FragmentListener.php b/vendor/symfony/http-kernel/EventListener/FragmentListener.php new file mode 100644 index 00000000..37bf15c3 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/FragmentListener.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\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Handles content fragments represented by special URIs. + * + * All URL paths starting with /_fragment are handled as + * content fragments by this listener. + * + * If throws an AccessDeniedHttpException exception if the request + * is not signed or if it is not an internal sub-request. + * + * @author Fabien Potencier + */ +class FragmentListener implements EventSubscriberInterface +{ + private $signer; + private $fragmentPath; + + /** + * Constructor. + * + * @param UriSigner $signer A UriSigner instance + * @param string $fragmentPath The path that triggers this listener + */ + public function __construct(UriSigner $signer, $fragmentPath = '/_fragment') + { + $this->signer = $signer; + $this->fragmentPath = $fragmentPath; + } + + /** + * Fixes request attributes when the path is '/_fragment'. + * + * @param GetResponseEvent $event A GetResponseEvent instance + * + * @throws AccessDeniedHttpException if the request does not come from a trusted IP. + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + if ($this->fragmentPath !== rawurldecode($request->getPathInfo())) { + return; + } + + if ($request->attributes->has('_controller')) { + // Is a sub-request: no need to parse _path but it should still be removed from query parameters as below. + $request->query->remove('_path'); + + return; + } + + if ($event->isMasterRequest()) { + $this->validateRequest($request); + } + + parse_str($request->query->get('_path', ''), $attributes); + $request->attributes->add($attributes); + $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', array()), $attributes)); + $request->query->remove('_path'); + } + + protected function validateRequest(Request $request) + { + // is the Request safe? + if (!$request->isMethodSafe(false)) { + throw new AccessDeniedHttpException(); + } + + // is the Request signed? + // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) + if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) { + return; + } + + throw new AccessDeniedHttpException(); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 48)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/LocaleListener.php b/vendor/symfony/http-kernel/EventListener/LocaleListener.php new file mode 100644 index 00000000..99fc7867 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/LocaleListener.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Initializes the locale based on the current request. + * + * @author Fabien Potencier + */ +class LocaleListener implements EventSubscriberInterface +{ + private $router; + private $defaultLocale; + private $requestStack; + + /** + * Constructor. + * + * @param RequestStack $requestStack A RequestStack instance + * @param string $defaultLocale The default locale + * @param RequestContextAwareInterface|null $router The router + */ + public function __construct(RequestStack $requestStack, $defaultLocale = 'en', RequestContextAwareInterface $router = null) + { + $this->defaultLocale = $defaultLocale; + $this->requestStack = $requestStack; + $this->router = $router; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->setDefaultLocale($this->defaultLocale); + + $this->setLocale($request); + $this->setRouterContext($request); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null !== $parentRequest = $this->requestStack->getParentRequest()) { + $this->setRouterContext($parentRequest); + } + } + + private function setLocale(Request $request) + { + if ($locale = $request->attributes->get('_locale')) { + $request->setLocale($locale); + } + } + + private function setRouterContext(Request $request) + { + if (null !== $this->router) { + $this->router->getContext()->setParameter('_locale', $request->getLocale()); + } + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Router to have access to the _locale + KernelEvents::REQUEST => array(array('onKernelRequest', 16)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ProfilerListener.php b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php new file mode 100644 index 00000000..c3772b68 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ProfilerListener collects data for the current request by listening to the kernel events. + * + * @author Fabien Potencier + */ +class ProfilerListener implements EventSubscriberInterface +{ + protected $profiler; + protected $matcher; + protected $onlyException; + protected $onlyMasterRequests; + protected $exception; + protected $profiles; + protected $requestStack; + protected $parents; + + /** + * Constructor. + * + * @param Profiler $profiler A Profiler instance + * @param RequestStack $requestStack A RequestStack instance + * @param RequestMatcherInterface|null $matcher A RequestMatcher instance + * @param bool $onlyException true if the profiler only collects data when an exception occurs, false otherwise + * @param bool $onlyMasterRequests true if the profiler only collects data when the request is a master request, false otherwise + */ + public function __construct(Profiler $profiler, RequestStack $requestStack, RequestMatcherInterface $matcher = null, $onlyException = false, $onlyMasterRequests = false) + { + $this->profiler = $profiler; + $this->matcher = $matcher; + $this->onlyException = (bool) $onlyException; + $this->onlyMasterRequests = (bool) $onlyMasterRequests; + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + $this->requestStack = $requestStack; + } + + /** + * Handles the onKernelException event. + * + * @param GetResponseForExceptionEvent $event A GetResponseForExceptionEvent instance + */ + public function onKernelException(GetResponseForExceptionEvent $event) + { + if ($this->onlyMasterRequests && !$event->isMasterRequest()) { + return; + } + + $this->exception = $event->getException(); + } + + /** + * Handles the onKernelResponse event. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + $master = $event->isMasterRequest(); + if ($this->onlyMasterRequests && !$master) { + return; + } + + if ($this->onlyException && null === $this->exception) { + return; + } + + $request = $event->getRequest(); + $exception = $this->exception; + $this->exception = null; + + if (null !== $this->matcher && !$this->matcher->matches($request)) { + return; + } + + if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) { + return; + } + + $this->profiles[$request] = $profile; + + $this->parents[$request] = $this->requestStack->getParentRequest(); + } + + public function onKernelTerminate(PostResponseEvent $event) + { + // attach children to parents + foreach ($this->profiles as $request) { + // isset call should be removed when requestStack is required + if (isset($this->parents[$request]) && null !== $parentRequest = $this->parents[$request]) { + if (isset($this->profiles[$parentRequest])) { + $this->profiles[$parentRequest]->addChild($this->profiles[$request]); + } + } + } + + // save profiles + foreach ($this->profiles as $request) { + $this->profiler->saveProfile($this->profiles[$request]); + } + + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => array('onKernelResponse', -100), + KernelEvents::EXCEPTION => 'onKernelException', + KernelEvents::TERMINATE => array('onKernelTerminate', -1024), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ResponseListener.php b/vendor/symfony/http-kernel/EventListener/ResponseListener.php new file mode 100644 index 00000000..eeb2b0fc --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ResponseListener.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ResponseListener fixes the Response headers based on the Request. + * + * @author Fabien Potencier + */ +class ResponseListener implements EventSubscriberInterface +{ + private $charset; + + public function __construct($charset) + { + $this->charset = $charset; + } + + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $response = $event->getResponse(); + + if (null === $response->getCharset()) { + $response->setCharset($this->charset); + } + + $response->prepare($event->getRequest()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/RouterListener.php b/vendor/symfony/http-kernel/EventListener/RouterListener.php new file mode 100644 index 00000000..3c46be86 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/RouterListener.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Initializes the context from the request and sets request attributes based on a matching route. + * + * @author Fabien Potencier + */ +class RouterListener implements EventSubscriberInterface +{ + private $matcher; + private $context; + private $logger; + private $requestStack; + + /** + * Constructor. + * + * @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher + * @param RequestStack $requestStack A RequestStack instance + * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) + * @param LoggerInterface|null $logger The logger + * + * @throws \InvalidArgumentException + */ + public function __construct($matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null) + { + if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) { + throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); + } + + if (null === $context && !$matcher instanceof RequestContextAwareInterface) { + throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.'); + } + + $this->matcher = $matcher; + $this->context = $context ?: $matcher->getContext(); + $this->requestStack = $requestStack; + $this->logger = $logger; + } + + private function setCurrentRequest(Request $request = null) + { + if (null !== $request) { + $this->context->fromRequest($request); + } + } + + /** + * After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator + * operates on the correct context again. + * + * @param FinishRequestEvent $event + */ + public function onKernelFinishRequest(FinishRequestEvent $event) + { + $this->setCurrentRequest($this->requestStack->getParentRequest()); + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + $this->setCurrentRequest($request); + + if ($request->attributes->has('_controller')) { + // routing is already done + return; + } + + // add attributes based on the request (routing) + try { + // matching a request is more powerful than matching a URL path + context, so try that first + if ($this->matcher instanceof RequestMatcherInterface) { + $parameters = $this->matcher->matchRequest($request); + } else { + $parameters = $this->matcher->match($request->getPathInfo()); + } + + if (null !== $this->logger) { + $this->logger->info('Matched route "{route}".', array( + 'route' => isset($parameters['_route']) ? $parameters['_route'] : 'n/a', + 'route_parameters' => $parameters, + 'request_uri' => $request->getUri(), + 'method' => $request->getMethod(), + )); + } + + $request->attributes->add($parameters); + unset($parameters['_route'], $parameters['_controller']); + $request->attributes->set('_route_params', $parameters); + } catch (ResourceNotFoundException $e) { + $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo()); + + if ($referer = $request->headers->get('referer')) { + $message .= sprintf(' (from "%s")', $referer); + } + + throw new NotFoundHttpException($message, $e); + } catch (MethodNotAllowedException $e) { + $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), implode(', ', $e->getAllowedMethods())); + + throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 32)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php b/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php new file mode 100644 index 00000000..36809b59 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Saves the session, in case it is still open, before sending the response/headers. + * + * This ensures several things in case the developer did not save the session explicitly: + * + * * If a session save handler without locking is used, it ensures the data is available + * on the next request, e.g. after a redirect. PHPs auto-save at script end via + * session_register_shutdown is executed after fastcgi_finish_request. So in this case + * the data could be missing the next request because it might not be saved the moment + * the new request is processed. + * * A locking save handler (e.g. the native 'files') circumvents concurrency problems like + * the one above. But by saving the session before long-running things in the terminate event, + * we ensure the session is not blocked longer than needed. + * * When regenerating the session ID no locking is involved in PHPs session design. See + * https://bugs.php.net/bug.php?id=61470 for a discussion. So in this case, the session must + * be saved anyway before sending the headers with the new session ID. Otherwise session + * data could get lost again for concurrent requests with the new ID. One result could be + * that you get logged out after just logging in. + * + * This listener should be executed as one of the last listeners, so that previous listeners + * can still operate on the open session. This prevents the overhead of restarting it. + * Listeners after closing the session can still work with the session as usual because + * Symfonys session implementation starts the session on demand. So writing to it after + * it is saved will just restart it. + * + * @author Tobias Schultze + */ +class SaveSessionListener implements EventSubscriberInterface +{ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $session = $event->getRequest()->getSession(); + if ($session && $session->isStarted()) { + $session->save(); + } + } + + public static function getSubscribedEvents() + { + return array( + // low priority but higher than StreamedResponseListener + KernelEvents::RESPONSE => array(array('onKernelResponse', -1000)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SessionListener.php b/vendor/symfony/http-kernel/EventListener/SessionListener.php new file mode 100644 index 00000000..39ebfd92 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SessionListener.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Container\ContainerInterface; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier + * + * @final since version 3.3 + */ +class SessionListener extends AbstractSessionListener +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getSession() + { + if (!$this->container->has('session')) { + return; + } + + return $this->container->get('session'); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php b/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php new file mode 100644 index 00000000..571cd74e --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.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\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * StreamedResponseListener is responsible for sending the Response + * to the client. + * + * @author Fabien Potencier + */ +class StreamedResponseListener implements EventSubscriberInterface +{ + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $response = $event->getResponse(); + + if ($response instanceof StreamedResponse) { + $response->send(); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => array('onKernelResponse', -1024), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SurrogateListener.php b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php new file mode 100644 index 00000000..dc815a21 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates. + * + * @author Fabien Potencier + */ +class SurrogateListener implements EventSubscriberInterface +{ + private $surrogate; + + /** + * Constructor. + * + * @param SurrogateInterface $surrogate An SurrogateInterface instance + */ + public function __construct(SurrogateInterface $surrogate = null) + { + $this->surrogate = $surrogate; + } + + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest() || null === $this->surrogate) { + return; + } + + $this->surrogate->addSurrogateControl($event->getResponse()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/TestSessionListener.php b/vendor/symfony/http-kernel/EventListener/TestSessionListener.php new file mode 100644 index 00000000..36abb422 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/TestSessionListener.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Container\ContainerInterface; + +/** + * Sets the session in the request. + * + * @author Fabien Potencier + * + * @final since version 3.3 + */ +class TestSessionListener extends AbstractTestSessionListener +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getSession() + { + if (!$this->container->has('session')) { + return; + } + + return $this->container->get('session'); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/TranslatorListener.php b/vendor/symfony/http-kernel/EventListener/TranslatorListener.php new file mode 100644 index 00000000..6967ad02 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/TranslatorListener.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * Synchronizes the locale between the request and the translator. + * + * @author Fabien Potencier + */ +class TranslatorListener implements EventSubscriberInterface +{ + private $translator; + private $requestStack; + + public function __construct(TranslatorInterface $translator, RequestStack $requestStack) + { + $this->translator = $translator; + $this->requestStack = $requestStack; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $this->setLocale($event->getRequest()); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null === $parentRequest = $this->requestStack->getParentRequest()) { + return; + } + + $this->setLocale($parentRequest); + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Locale listener + KernelEvents::REQUEST => array(array('onKernelRequest', 10)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } + + private function setLocale(Request $request) + { + try { + $this->translator->setLocale($request->getLocale()); + } catch (\InvalidArgumentException $e) { + $this->translator->setLocale($request->getDefaultLocale()); + } + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php new file mode 100644 index 00000000..b96c7814 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Validates Requests. + * + * @author Magnus Nordlander + */ +class ValidateRequestListener implements EventSubscriberInterface +{ + /** + * Performs the validation. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + $request = $event->getRequest(); + + if ($request::getTrustedProxies()) { + $request->getClientIps(); + } + + $request->getHost(); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array( + array('onKernelRequest', 256), + ), + ); + } +} diff --git a/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php new file mode 100644 index 00000000..79d8639a --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.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\HttpKernel\Exception; + +/** + * AccessDeniedHttpException. + * + * @author Fabien Potencier + * @author Christophe Coevoet + */ +class AccessDeniedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(403, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php new file mode 100644 index 00000000..5f68172a --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.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\HttpKernel\Exception; + +/** + * BadRequestHttpException. + * + * @author Ben Ramsey + */ +class BadRequestHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(400, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ConflictHttpException.php b/vendor/symfony/http-kernel/Exception/ConflictHttpException.php new file mode 100644 index 00000000..34d738ed --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ConflictHttpException.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\HttpKernel\Exception; + +/** + * ConflictHttpException. + * + * @author Ben Ramsey + */ +class ConflictHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(409, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/GoneHttpException.php b/vendor/symfony/http-kernel/Exception/GoneHttpException.php new file mode 100644 index 00000000..16ea223f --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/GoneHttpException.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\HttpKernel\Exception; + +/** + * GoneHttpException. + * + * @author Ben Ramsey + */ +class GoneHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(410, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpException.php b/vendor/symfony/http-kernel/Exception/HttpException.php new file mode 100644 index 00000000..e8e37605 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpException.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\HttpKernel\Exception; + +/** + * HttpException. + * + * @author Kris Wallsmith + */ +class HttpException extends \RuntimeException implements HttpExceptionInterface +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = array(), $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } + + /** + * Set response headers. + * + * @param array $headers Response headers + */ + public function setHeaders(array $headers) + { + $this->headers = $headers; + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php new file mode 100644 index 00000000..8aa50a9f --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Interface for HTTP error exceptions. + * + * @author Kris Wallsmith + */ +interface HttpExceptionInterface +{ + /** + * Returns the status code. + * + * @return int An HTTP response status code + */ + public function getStatusCode(); + + /** + * Returns response headers. + * + * @return array Response headers + */ + public function getHeaders(); +} diff --git a/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php new file mode 100644 index 00000000..0c4b9431 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.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\HttpKernel\Exception; + +/** + * LengthRequiredHttpException. + * + * @author Ben Ramsey + */ +class LengthRequiredHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(411, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php new file mode 100644 index 00000000..78dd26bf --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * MethodNotAllowedHttpException. + * + * @author Kris Wallsmith + */ +class MethodNotAllowedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param array $allow An array of allowed methods + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct(array $allow, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('Allow' => strtoupper(implode(', ', $allow))); + + parent::__construct(405, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php new file mode 100644 index 00000000..cc6be4ba --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.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\HttpKernel\Exception; + +/** + * NotAcceptableHttpException. + * + * @author Ben Ramsey + */ +class NotAcceptableHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(406, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php new file mode 100644 index 00000000..4639e379 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.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\HttpKernel\Exception; + +/** + * NotFoundHttpException. + * + * @author Fabien Potencier + */ +class NotFoundHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(404, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php new file mode 100644 index 00000000..9df0e7b4 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.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\HttpKernel\Exception; + +/** + * PreconditionFailedHttpException. + * + * @author Ben Ramsey + */ +class PreconditionFailedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(412, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php new file mode 100644 index 00000000..08ebca22 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * PreconditionRequiredHttpException. + * + * @author Ben Ramsey + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class PreconditionRequiredHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(428, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php new file mode 100644 index 00000000..32b9e2d2 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * ServiceUnavailableHttpException. + * + * @author Ben Ramsey + */ +class ServiceUnavailableHttpException extends HttpException +{ + /** + * Constructor. + * + * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(503, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php new file mode 100644 index 00000000..ab86e092 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * TooManyRequestsHttpException. + * + * @author Ben Ramsey + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class TooManyRequestsHttpException extends HttpException +{ + /** + * Constructor. + * + * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(429, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php new file mode 100644 index 00000000..0dfe42db --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * UnauthorizedHttpException. + * + * @author Ben Ramsey + */ +class UnauthorizedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $challenge WWW-Authenticate challenge string + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('WWW-Authenticate' => $challenge); + + parent::__construct(401, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php new file mode 100644 index 00000000..eb13f563 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.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\HttpKernel\Exception; + +/** + * UnprocessableEntityHttpException. + * + * @author Steve Hutchins + */ +class UnprocessableEntityHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(422, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php new file mode 100644 index 00000000..a9d8fa08 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.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\HttpKernel\Exception; + +/** + * UnsupportedMediaTypeHttpException. + * + * @author Ben Ramsey + */ +class UnsupportedMediaTypeHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(415, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php new file mode 100644 index 00000000..0d4d26b6 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * Implements Surrogate rendering strategy. + * + * @author Fabien Potencier + */ +abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer +{ + private $surrogate; + private $inlineStrategy; + private $signer; + + /** + * Constructor. + * + * The "fallback" strategy when surrogate is not available should always be an + * instance of InlineFragmentRenderer. + * + * @param SurrogateInterface $surrogate An Surrogate instance + * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported + * @param UriSigner $signer + */ + public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null) + { + $this->surrogate = $surrogate; + $this->inlineStrategy = $inlineStrategy; + $this->signer = $signer; + } + + /** + * {@inheritdoc} + * + * Note that if the current Request has no surrogate capability, this method + * falls back to use the inline rendering strategy. + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + * * comment: a comment to add when returning the surrogate tag + * + * Note, that not all surrogate strategies support all options. For now + * 'alt' and 'comment' are only supported by ESI. + * + * @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface + */ + public function render($uri, Request $request, array $options = array()) + { + if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) { + if ($uri instanceof ControllerReference && $this->containsNonScalars($uri->attributes)) { + @trigger_error('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is deprecated since version 3.1, and will be removed in 4.0. Use a different rendering strategy or pass scalar values.', E_USER_DEPRECATED); + } + + return $this->inlineStrategy->render($uri, $request, $options); + } + + if ($uri instanceof ControllerReference) { + $uri = $this->generateSignedFragmentUri($uri, $request); + } + + $alt = isset($options['alt']) ? $options['alt'] : null; + if ($alt instanceof ControllerReference) { + $alt = $this->generateSignedFragmentUri($alt, $request); + } + + $tag = $this->surrogate->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : ''); + + return new Response($tag); + } + + private function generateSignedFragmentUri($uri, Request $request) + { + if (null === $this->signer) { + throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); + } + + // we need to sign the absolute URI, but want to return the path only. + $fragmentUri = $this->signer->sign($this->generateFragmentUri($uri, $request, true)); + + return substr($fragmentUri, strlen($request->getSchemeAndHttpHost())); + } + + private function containsNonScalars(array $values) + { + foreach ($values as $value) { + if (is_array($value) && $this->containsNonScalars($value)) { + return true; + } elseif (!is_scalar($value) && null !== $value) { + return true; + } + } + + return false; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php new file mode 100644 index 00000000..a4570e3b --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.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\HttpKernel\Fragment; + +/** + * Implements the ESI rendering strategy. + * + * @author Fabien Potencier + */ +class EsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'esi'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentHandler.php b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php new file mode 100644 index 00000000..0d0a0424 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Renders a URI that represents a resource fragment. + * + * This class handles the rendering of resource fragments that are included into + * a main resource. The handling of the rendering is managed by specialized renderers. + * + * @author Fabien Potencier + * + * @see FragmentRendererInterface + */ +class FragmentHandler +{ + private $debug; + private $renderers = array(); + private $requestStack; + + /** + * Constructor. + * + * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests + * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances + * @param bool $debug Whether the debug mode is enabled or not + */ + public function __construct(RequestStack $requestStack, array $renderers = array(), $debug = false) + { + $this->requestStack = $requestStack; + foreach ($renderers as $renderer) { + $this->addRenderer($renderer); + } + $this->debug = $debug; + } + + /** + * Adds a renderer. + * + * @param FragmentRendererInterface $renderer A FragmentRendererInterface instance + */ + public function addRenderer(FragmentRendererInterface $renderer) + { + $this->renderers[$renderer->getName()] = $renderer; + } + + /** + * Renders a URI and returns the Response content. + * + * Available options: + * + * * ignore_errors: true to return an empty string in case of an error + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param string $renderer The renderer name + * @param array $options An array of options + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \InvalidArgumentException when the renderer does not exist + * @throws \LogicException when no master request is being handled + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + if (!isset($options['ignore_errors'])) { + $options['ignore_errors'] = !$this->debug; + } + + if (!isset($this->renderers[$renderer])) { + throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer)); + } + + if (!$request = $this->requestStack->getCurrentRequest()) { + throw new \LogicException('Rendering a fragment can only be done when handling a Request.'); + } + + return $this->deliver($this->renderers[$renderer]->render($uri, $request, $options)); + } + + /** + * Delivers the Response as a string. + * + * When the Response is a StreamedResponse, the content is streamed immediately + * instead of being returned. + * + * @param Response $response A Response instance + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \RuntimeException when the Response is not successful + */ + protected function deliver(Response $response) + { + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->requestStack->getCurrentRequest()->getUri(), $response->getStatusCode())); + } + + if (!$response instanceof StreamedResponse) { + return $response->getContent(); + } + + $response->sendContent(); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php new file mode 100644 index 00000000..b177c3ac --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpFoundation\Response; + +/** + * Interface implemented by all rendering strategies. + * + * @author Fabien Potencier + */ +interface FragmentRendererInterface +{ + /** + * Renders a URI and returns the Response content. + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param Request $request A Request instance + * @param array $options An array of options + * + * @return Response A Response instance + */ + public function render($uri, Request $request, array $options = array()); + + /** + * Gets the name of the strategy. + * + * @return string The strategy name + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php new file mode 100644 index 00000000..ec2a4071 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Templating\EngineInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\UriSigner; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Loader\ExistsLoaderInterface; + +/** + * Implements the Hinclude rendering strategy. + * + * @author Fabien Potencier + */ +class HIncludeFragmentRenderer extends RoutableFragmentRenderer +{ + private $globalDefaultTemplate; + private $signer; + private $templating; + private $charset; + + /** + * Constructor. + * + * @param EngineInterface|Environment $templating An EngineInterface or a Twig instance + * @param UriSigner $signer A UriSigner instance + * @param string $globalDefaultTemplate The global default content (it can be a template name or the content) + * @param string $charset + */ + public function __construct($templating = null, UriSigner $signer = null, $globalDefaultTemplate = null, $charset = 'utf-8') + { + $this->setTemplating($templating); + $this->globalDefaultTemplate = $globalDefaultTemplate; + $this->signer = $signer; + $this->charset = $charset; + } + + /** + * Sets the templating engine to use to render the default content. + * + * @param EngineInterface|Environment|null $templating An EngineInterface or an Environment instance + * + * @throws \InvalidArgumentException + */ + public function setTemplating($templating) + { + if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof Environment) { + throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of Twig\Environment or Symfony\Component\Templating\EngineInterface'); + } + + $this->templating = $templating; + } + + /** + * Checks if a templating engine has been set. + * + * @return bool true if the templating engine has been set, false otherwise + */ + public function hasTemplating() + { + return null !== $this->templating; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * default: The default content (it can be a template name or the content) + * * id: An optional hx:include tag id attribute + * * attributes: An optional array of hx:include tag attributes + */ + public function render($uri, Request $request, array $options = array()) + { + if ($uri instanceof ControllerReference) { + if (null === $this->signer) { + throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.'); + } + + // we need to sign the absolute URI, but want to return the path only. + $uri = substr($this->signer->sign($this->generateFragmentUri($uri, $request, true)), strlen($request->getSchemeAndHttpHost())); + } + + // We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content. + $uri = str_replace('&', '&', $uri); + + $template = isset($options['default']) ? $options['default'] : $this->globalDefaultTemplate; + if (null !== $this->templating && $template && $this->templateExists($template)) { + $content = $this->templating->render($template); + } else { + $content = $template; + } + + $attributes = isset($options['attributes']) && is_array($options['attributes']) ? $options['attributes'] : array(); + if (isset($options['id']) && $options['id']) { + $attributes['id'] = $options['id']; + } + $renderedAttributes = ''; + if (count($attributes) > 0) { + $flags = ENT_QUOTES | ENT_SUBSTITUTE; + foreach ($attributes as $attribute => $value) { + $renderedAttributes .= sprintf( + ' %s="%s"', + htmlspecialchars($attribute, $flags, $this->charset, false), + htmlspecialchars($value, $flags, $this->charset, false) + ); + } + } + + return new Response(sprintf('%s', $uri, $renderedAttributes, $content)); + } + + /** + * @param string $template + * + * @return bool + */ + private function templateExists($template) + { + if ($this->templating instanceof EngineInterface) { + try { + return $this->templating->exists($template); + } catch (\InvalidArgumentException $e) { + return false; + } + } + + $loader = $this->templating->getLoader(); + if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) { + return $loader->exists($template); + } + + try { + if (method_exists($loader, 'getSourceContext')) { + $loader->getSourceContext($template); + } else { + $loader->getSource($template); + } + + return true; + } catch (LoaderError $e) { + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'hinclude'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php new file mode 100644 index 00000000..437b40bf --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel. + * + * @author Fabien Potencier + */ +class InlineFragmentRenderer extends RoutableFragmentRenderer +{ + private $kernel; + private $dispatcher; + + /** + * Constructor. + * + * @param HttpKernelInterface $kernel A HttpKernelInterface instance + * @param EventDispatcherInterface $dispatcher A EventDispatcherInterface instance + */ + public function __construct(HttpKernelInterface $kernel, EventDispatcherInterface $dispatcher = null) + { + $this->kernel = $kernel; + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + */ + public function render($uri, Request $request, array $options = array()) + { + $reference = null; + if ($uri instanceof ControllerReference) { + $reference = $uri; + + // Remove attributes from the generated URI because if not, the Symfony + // routing system will use them to populate the Request attributes. We don't + // want that as we want to preserve objects (so we manually set Request attributes + // below instead) + $attributes = $reference->attributes; + $reference->attributes = array(); + + // The request format and locale might have been overridden by the user + foreach (array('_format', '_locale') as $key) { + if (isset($attributes[$key])) { + $reference->attributes[$key] = $attributes[$key]; + } + } + + $uri = $this->generateFragmentUri($uri, $request, false, false); + + $reference->attributes = array_merge($attributes, $reference->attributes); + } + + $subRequest = $this->createSubRequest($uri, $request); + + // override Request attributes as they can be objects (which are not supported by the generated URI) + if (null !== $reference) { + $subRequest->attributes->add($reference->attributes); + } + + $level = ob_get_level(); + try { + return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + // we dispatch the exception event to trigger the logging + // the response that comes back is simply ignored + if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) { + $event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); + + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + } + + // let's clean up the output buffers that were created by the sub-request + Response::closeOutputBuffers($level, false); + + if (isset($options['alt'])) { + $alt = $options['alt']; + unset($options['alt']); + + return $this->render($alt, $request, $options); + } + + if (!isset($options['ignore_errors']) || !$options['ignore_errors']) { + throw $e; + } + + return new Response(); + } + } + + protected function createSubRequest($uri, Request $request) + { + $cookies = $request->cookies->all(); + $server = $request->server->all(); + + // Override the arguments to emulate a sub-request. + // Sub-request object will point to localhost as client ip and real client ip + // will be included into trusted header for client ip + try { + if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { + $currentXForwardedFor = $request->headers->get('X_FORWARDED_FOR', ''); + + $server['HTTP_X_FORWARDED_FOR'] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp(); + } elseif (method_exists(Request::class, 'getTrustedHeaderName') && $trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP, false)) { + $currentXForwardedFor = $request->headers->get($trustedHeaderName, ''); + + $server['HTTP_'.$trustedHeaderName] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp(); + } + } catch (\InvalidArgumentException $e) { + // Do nothing + } + + $server['REMOTE_ADDR'] = '127.0.0.1'; + unset($server['HTTP_IF_MODIFIED_SINCE']); + unset($server['HTTP_IF_NONE_MATCH']); + + $subRequest = Request::create($uri, 'get', array(), $cookies, array(), $server); + if ($request->headers->has('Surrogate-Capability')) { + $subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability')); + } + + if ($session = $request->getSession()) { + $subRequest->setSession($session); + } + + return $subRequest; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'inline'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php new file mode 100644 index 00000000..d7eeb89a --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; + +/** + * Adds the possibility to generate a fragment URI for a given Controller. + * + * @author Fabien Potencier + */ +abstract class RoutableFragmentRenderer implements FragmentRendererInterface +{ + private $fragmentPath = '/_fragment'; + + /** + * Sets the fragment path that triggers the fragment listener. + * + * @param string $path The path + * + * @see FragmentListener + */ + public function setFragmentPath($path) + { + $this->fragmentPath = $path; + } + + /** + * Generates a fragment URI for a given controller. + * + * @param ControllerReference $reference A ControllerReference instance + * @param Request $request A Request instance + * @param bool $absolute Whether to generate an absolute URL or not + * @param bool $strict Whether to allow non-scalar attributes or not + * + * @return string A fragment URI + */ + protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false, $strict = true) + { + if ($strict) { + $this->checkNonScalar($reference->attributes); + } + + // We need to forward the current _format and _locale values as we don't have + // a proper routing pattern to do the job for us. + // This makes things inconsistent if you switch from rendering a controller + // to rendering a route if the route pattern does not contain the special + // _format and _locale placeholders. + if (!isset($reference->attributes['_format'])) { + $reference->attributes['_format'] = $request->getRequestFormat(); + } + if (!isset($reference->attributes['_locale'])) { + $reference->attributes['_locale'] = $request->getLocale(); + } + + $reference->attributes['_controller'] = $reference->controller; + + $reference->query['_path'] = http_build_query($reference->attributes, '', '&'); + + $path = $this->fragmentPath.'?'.http_build_query($reference->query, '', '&'); + + if ($absolute) { + return $request->getUriForPath($path); + } + + return $request->getBaseUrl().$path; + } + + private function checkNonScalar($values) + { + foreach ($values as $key => $value) { + if (is_array($value)) { + $this->checkNonScalar($value); + } elseif (!is_scalar($value) && null !== $value) { + throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php new file mode 100644 index 00000000..45e7122f --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.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\HttpKernel\Fragment; + +/** + * Implements the SSI rendering strategy. + * + * @author Sebastian Krebs + */ +class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ssi'; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php b/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php new file mode 100644 index 00000000..af94bea9 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Abstract class implementing Surrogate capabilities to Request and Response instances. + * + * @author Fabien Potencier + * @author Robin Chalas + */ +abstract class AbstractSurrogate implements SurrogateInterface +{ + protected $contentTypes; + protected $phpEscapeMap = array( + array('', '', '', ''), + ); + + /** + * Constructor. + * + * @param array $contentTypes An array of content-type that should be parsed for Surrogate information + * (default: text/html, text/xml, application/xhtml+xml, and application/xml) + */ + public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xhtml+xml', 'application/xml')) + { + $this->contentTypes = $contentTypes; + } + + /** + * Returns a new cache strategy instance. + * + * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + */ + public function createCacheStrategy() + { + return new ResponseCacheStrategy(); + } + + /** + * {@inheritdoc} + */ + public function hasSurrogateCapability(Request $request) + { + if (null === $value = $request->headers->get('Surrogate-Capability')) { + return false; + } + + return false !== strpos($value, sprintf('%s/1.0', strtoupper($this->getName()))); + } + + /** + * {@inheritdoc} + */ + public function addSurrogateCapability(Request $request) + { + $current = $request->headers->get('Surrogate-Capability'); + $new = sprintf('symfony="%s/1.0"', strtoupper($this->getName())); + + $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); + } + + /** + * {@inheritdoc} + */ + public function needsParsing(Response $response) + { + if (!$control = $response->headers->get('Surrogate-Control')) { + return false; + } + + $pattern = sprintf('#content="[^"]*%s/1.0[^"]*"#', strtoupper($this->getName())); + + return (bool) preg_match($pattern, $control); + } + + /** + * {@inheritdoc} + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors) + { + $subRequest = Request::create($uri, Request::METHOD_GET, array(), $cache->getRequest()->cookies->all(), array(), $cache->getRequest()->server->all()); + + try { + $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true); + + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode())); + } + + return $response->getContent(); + } catch (\Exception $e) { + if ($alt) { + return $this->handle($cache, $alt, '', $ignoreErrors); + } + + if (!$ignoreErrors) { + throw $e; + } + } + } + + /** + * Remove the Surrogate from the Surrogate-Control header. + * + * @param Response $response + */ + protected function removeFromControl(Response $response) + { + if (!$response->headers->has('Surrogate-Control')) { + return; + } + + $value = $response->headers->get('Surrogate-Control'); + $upperName = strtoupper($this->getName()); + + if (sprintf('content="%s/1.0"', $upperName) == $value) { + $response->headers->remove('Surrogate-Control'); + } elseif (preg_match(sprintf('#,\s*content="%s/1.0"#', $upperName), $value)) { + $response->headers->set('Surrogate-Control', preg_replace(sprintf('#,\s*content="%s/1.0"#', $upperName), '', $value)); + } elseif (preg_match(sprintf('#content="%s/1.0",\s*#', $upperName), $value)) { + $response->headers->set('Surrogate-Control', preg_replace(sprintf('#content="%s/1.0",\s*#', $upperName), '', $value)); + } + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Esi.php b/vendor/symfony/http-kernel/HttpCache/Esi.php new file mode 100644 index 00000000..d09907ea --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Esi.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\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Esi implements the ESI capabilities to Request and Response instances. + * + * For more information, read the following W3C notes: + * + * * ESI Language Specification 1.0 (http://www.w3.org/TR/esi-lang) + * + * * Edge Architecture Specification (http://www.w3.org/TR/edge-arch) + * + * @author Fabien Potencier + */ +class Esi extends AbstractSurrogate +{ + public function getName() + { + return 'esi'; + } + + /** + * {@inheritdoc} + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), 'headers->set('Surrogate-Control', 'content="ESI/1.0"'); + } + } + + /** + * {@inheritdoc} + */ + public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = '') + { + $html = sprintf('', + $uri, + $ignoreErrors ? ' onerror="continue"' : '', + $alt ? sprintf(' alt="%s"', $alt) : '' + ); + + if (!empty($comment)) { + return sprintf("\n%s", $comment, $html); + } + + return $html; + } + + /** + * {@inheritdoc} + */ + public function process(Request $request, Response $response) + { + $type = $response->headers->get('Content-Type'); + if (empty($type)) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!in_array($parts[0], $this->contentTypes)) { + return $response; + } + + // we don't use a proper XML parser here as we can have ESI tags in a plain text response + $content = $response->getContent(); + $content = preg_replace('#.*?#s', '', $content); + $content = preg_replace('#]+>#s', '', $content); + + $chunks = preg_split('##', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); + + $i = 1; + while (isset($chunks[$i])) { + $options = array(); + preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['src'])) { + throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); + } + + $chunks[$i] = sprintf('surrogate->handle($this, %s, %s, %s) ?>'."\n", + var_export($options['src'], true), + var_export(isset($options['alt']) ? $options['alt'] : '', true), + isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false' + ); + ++$i; + $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); + ++$i; + } + $content = implode('', $chunks); + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'ESI'); + + // remove ESI/1.0 from the Surrogate-Control header + $this->removeFromControl($response); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/HttpCache.php b/vendor/symfony/http-kernel/HttpCache/HttpCache.php new file mode 100644 index 00000000..be2bc63d --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/HttpCache.php @@ -0,0 +1,737 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Cache provides HTTP caching. + * + * @author Fabien Potencier + */ +class HttpCache implements HttpKernelInterface, TerminableInterface +{ + private $kernel; + private $store; + private $request; + private $surrogate; + private $surrogateCacheStrategy; + private $options = array(); + private $traces = array(); + + /** + * Constructor. + * + * The available options are: + * + * * debug: If true, the traces are added as a HTTP header to ease debugging + * + * * default_ttl The number of seconds that a cache entry should be considered + * fresh when no explicit freshness information is provided in + * a response. Explicit Cache-Control or Expires headers + * override this value. (default: 0) + * + * * private_headers Set of request headers that trigger "private" cache-control behavior + * on responses that don't explicitly state whether the response is + * public or private via a Cache-Control directive. (default: Authorization and Cookie) + * + * * allow_reload Specifies whether the client can force a cache reload by including a + * Cache-Control "no-cache" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * allow_revalidate Specifies whether the client can force a cache revalidate by including + * a Cache-Control "max-age=0" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the + * Response TTL precision is a second) during which the cache can immediately return + * a stale response while it revalidates it in the background (default: 2). + * This setting is overridden by the stale-while-revalidate HTTP Cache-Control + * extension (see RFC 5861). + * + * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which + * the cache can serve a stale response when an error is encountered (default: 60). + * This setting is overridden by the stale-if-error HTTP Cache-Control extension + * (see RFC 5861). + * + * @param HttpKernelInterface $kernel An HttpKernelInterface instance + * @param StoreInterface $store A Store instance + * @param SurrogateInterface $surrogate A SurrogateInterface instance + * @param array $options An array of options + */ + public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array()) + { + $this->store = $store; + $this->kernel = $kernel; + $this->surrogate = $surrogate; + + // needed in case there is a fatal error because the backend is too slow to respond + register_shutdown_function(array($this->store, 'cleanup')); + + $this->options = array_merge(array( + 'debug' => false, + 'default_ttl' => 0, + 'private_headers' => array('Authorization', 'Cookie'), + 'allow_reload' => false, + 'allow_revalidate' => false, + 'stale_while_revalidate' => 2, + 'stale_if_error' => 60, + ), $options); + } + + /** + * Gets the current store. + * + * @return StoreInterface $store A StoreInterface instance + */ + public function getStore() + { + return $this->store; + } + + /** + * Returns an array of events that took place during processing of the last request. + * + * @return array An array of events + */ + public function getTraces() + { + return $this->traces; + } + + /** + * Returns a log message for the events of the last request processing. + * + * @return string A log message + */ + public function getLog() + { + $log = array(); + foreach ($this->traces as $request => $traces) { + $log[] = sprintf('%s: %s', $request, implode(', ', $traces)); + } + + return implode('; ', $log); + } + + /** + * Gets the Request instance associated with the master request. + * + * @return Request A Request instance + */ + public function getRequest() + { + return $this->request; + } + + /** + * Gets the Kernel instance. + * + * @return HttpKernelInterface An HttpKernelInterface instance + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Gets the Surrogate instance. + * + * @return SurrogateInterface A Surrogate instance + * + * @throws \LogicException + */ + public function getSurrogate() + { + return $this->surrogate; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->traces = array(); + $this->request = $request; + if (null !== $this->surrogate) { + $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); + } + } + + $this->traces[$this->getTraceKey($request)] = array(); + + if (!$request->isMethodSafe(false)) { + $response = $this->invalidate($request, $catch); + } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { + $response = $this->pass($request, $catch); + } elseif ($this->options['allow_reload'] && $request->isNoCache()) { + /* + If allow_reload is configured and the client requests "Cache-Control: no-cache", + reload the cache by fetching a fresh response and caching it (if possible). + */ + $this->record($request, 'reload'); + $response = $this->fetch($request, $catch); + } else { + $response = $this->lookup($request, $catch); + } + + $this->restoreResponseBody($request, $response); + + if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { + $response->headers->set('X-Symfony-Cache', $this->getLog()); + } + + if (null !== $this->surrogate) { + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->surrogateCacheStrategy->update($response); + } else { + $this->surrogateCacheStrategy->add($response); + } + } + + $response->prepare($request); + + $response->isNotModified($request); + + return $response; + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + if ($this->getKernel() instanceof TerminableInterface) { + $this->getKernel()->terminate($request, $response); + } + } + + /** + * Forwards the Request to the backend without storing the Response in the cache. + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function pass(Request $request, $catch = false) + { + $this->record($request, 'pass'); + + return $this->forward($request, $catch); + } + + /** + * Invalidates non-safe methods (like POST, PUT, and DELETE). + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + * + * @see RFC2616 13.10 + */ + protected function invalidate(Request $request, $catch = false) + { + $response = $this->pass($request, $catch); + + // invalidate only when the response is successful + if ($response->isSuccessful() || $response->isRedirect()) { + try { + $this->store->invalidate($request); + + // As per the RFC, invalidate Location and Content-Location URLs if present + foreach (array('Location', 'Content-Location') as $header) { + if ($uri = $response->headers->get($header)) { + $subRequest = Request::create($uri, 'get', array(), array(), array(), $request->server->all()); + + $this->store->invalidate($subRequest); + } + } + + $this->record($request, 'invalidate'); + } catch (\Exception $e) { + $this->record($request, 'invalidate-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + } + + return $response; + } + + /** + * Lookups a Response from the cache for the given Request. + * + * When a matching cache entry is found and is fresh, it uses it as the + * response without forwarding any request to the backend. When a matching + * cache entry is found but is stale, it attempts to "validate" the entry with + * the backend using conditional GET. When no matching cache entry is found, + * it triggers "miss" processing. + * + * @param Request $request A Request instance + * @param bool $catch whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + */ + protected function lookup(Request $request, $catch = false) + { + try { + $entry = $this->store->lookup($request); + } catch (\Exception $e) { + $this->record($request, 'lookup-failed'); + + if ($this->options['debug']) { + throw $e; + } + + return $this->pass($request, $catch); + } + + if (null === $entry) { + $this->record($request, 'miss'); + + return $this->fetch($request, $catch); + } + + if (!$this->isFreshEnough($request, $entry)) { + $this->record($request, 'stale'); + + return $this->validate($request, $entry, $catch); + } + + $this->record($request, 'fresh'); + + $entry->headers->set('Age', $entry->getAge()); + + return $entry; + } + + /** + * Validates that a cache entry is fresh. + * + * The original request is used as a template for a conditional + * GET request with the backend. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance to validate + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function validate(Request $request, Response $entry, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // add our cached last-modified validator + $subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified')); + + // Add our cached etag validator to the environment. + // We keep the etags from the client to handle the case when the client + // has a different private valid entry which is not cached here. + $cachedEtags = $entry->getEtag() ? array($entry->getEtag()) : array(); + $requestEtags = $request->getETags(); + if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) { + $subRequest->headers->set('if_none_match', implode(', ', $etags)); + } + + $response = $this->forward($subRequest, $catch, $entry); + + if (304 == $response->getStatusCode()) { + $this->record($request, 'valid'); + + // return the response and not the cache entry if the response is valid but not cached + $etag = $response->getEtag(); + if ($etag && in_array($etag, $requestEtags) && !in_array($etag, $cachedEtags)) { + return $response; + } + + $entry = clone $entry; + $entry->headers->remove('Date'); + + foreach (array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified') as $name) { + if ($response->headers->has($name)) { + $entry->headers->set($name, $response->headers->get($name)); + } + } + + $response = $entry; + } else { + $this->record($request, 'invalid'); + } + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Unconditionally fetches a fresh response from the backend and + * stores it in the cache if is cacheable. + * + * @param Request $request A Request instance + * @param bool $catch whether to process exceptions + * + * @return Response A Response instance + */ + protected function fetch(Request $request, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // avoid that the backend sends no content + $subRequest->headers->remove('if_modified_since'); + $subRequest->headers->remove('if_none_match'); + + $response = $this->forward($subRequest, $catch); + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Forwards the Request to the backend and returns the Response. + * + * All backend requests (cache passes, fetches, cache validations) + * run through this method. + * + * @param Request $request A Request instance + * @param bool $catch Whether to catch exceptions or not + * @param Response $entry A Response instance (the stale entry if present, null otherwise) + * + * @return Response A Response instance + */ + protected function forward(Request $request, $catch = false, Response $entry = null) + { + if ($this->surrogate) { + $this->surrogate->addSurrogateCapability($request); + } + + // modify the X-Forwarded-For header if needed + $forwardedFor = $request->headers->get('X-Forwarded-For'); + if ($forwardedFor) { + $request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR')); + } else { + $request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR')); + } + + // fix the client IP address by setting it to 127.0.0.1 as HttpCache + // is always called from the same process as the backend. + $request->server->set('REMOTE_ADDR', '127.0.0.1'); + + // make sure HttpCache is a trusted proxy + if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) { + $trustedProxies[] = '127.0.0.1'; + Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_ALL); + } + + // always a "master" request (as the real master request can be in cache) + $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch); + // FIXME: we probably need to also catch exceptions if raw === true + + // we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC + if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) { + if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) { + $age = $this->options['stale_if_error']; + } + + if (abs($entry->getTtl()) < $age) { + $this->record($request, 'stale-if-error'); + + return $entry; + } + } + + /* + RFC 7231 Sect. 7.1.1.2 says that a server that does not have a reasonably accurate + clock MUST NOT send a "Date" header, although it MUST send one in most other cases + except for 1xx or 5xx responses where it MAY do so. + + Anyway, a client that received a message without a "Date" header MUST add it. + */ + if (!$response->headers->has('Date')) { + $response->setDate(\DateTime::createFromFormat('U', time())); + } + + $this->processResponseBody($request, $response); + + if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { + $response->setPrivate(); + } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { + $response->setTtl($this->options['default_ttl']); + } + + return $response; + } + + /** + * Checks whether the cache entry is "fresh enough" to satisfy the Request. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance + * + * @return bool true if the cache entry if fresh enough, false otherwise + */ + protected function isFreshEnough(Request $request, Response $entry) + { + if (!$entry->isFresh()) { + return $this->lock($request, $entry); + } + + if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) { + return $maxAge > 0 && $maxAge >= $entry->getAge(); + } + + return true; + } + + /** + * Locks a Request during the call to the backend. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance + * + * @return bool true if the cache entry can be returned even if it is staled, false otherwise + */ + protected function lock(Request $request, Response $entry) + { + // try to acquire a lock to call the backend + $lock = $this->store->lock($request); + + if (true === $lock) { + // we have the lock, call the backend + return false; + } + + // there is already another process calling the backend + + // May we serve a stale response? + if ($this->mayServeStaleWhileRevalidate($entry)) { + $this->record($request, 'stale-while-revalidate'); + + return true; + } + + // wait for the lock to be released + if ($this->waitForLock($request)) { + // replace the current entry with the fresh one + $new = $this->lookup($request); + $entry->headers = $new->headers; + $entry->setContent($new->getContent()); + $entry->setStatusCode($new->getStatusCode()); + $entry->setProtocolVersion($new->getProtocolVersion()); + foreach ($new->headers->getCookies() as $cookie) { + $entry->headers->setCookie($cookie); + } + } else { + // backend is slow as hell, send a 503 response (to avoid the dog pile effect) + $entry->setStatusCode(503); + $entry->setContent('503 Service Unavailable'); + $entry->headers->set('Retry-After', 10); + } + + return true; + } + + /** + * Writes the Response to the cache. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @throws \Exception + */ + protected function store(Request $request, Response $response) + { + try { + $this->store->write($request, $response); + + $this->record($request, 'store'); + + $response->headers->set('Age', $response->getAge()); + } catch (\Exception $e) { + $this->record($request, 'store-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + + // now that the response is cached, release the lock + $this->store->unlock($request); + } + + /** + * Restores the Response body. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + */ + private function restoreResponseBody(Request $request, Response $response) + { + if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) { + $response->setContent(null); + $response->headers->remove('X-Body-Eval'); + $response->headers->remove('X-Body-File'); + + return; + } + + if ($response->headers->has('X-Body-Eval')) { + ob_start(); + + if ($response->headers->has('X-Body-File')) { + include $response->headers->get('X-Body-File'); + } else { + eval('; ?>'.$response->getContent().'setContent(ob_get_clean()); + $response->headers->remove('X-Body-Eval'); + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', strlen($response->getContent())); + } + } elseif ($response->headers->has('X-Body-File')) { + $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); + } else { + return; + } + + $response->headers->remove('X-Body-File'); + } + + protected function processResponseBody(Request $request, Response $response) + { + if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) { + $this->surrogate->process($request, $response); + } + } + + /** + * Checks if the Request includes authorization or other sensitive information + * that should cause the Response to be considered private by default. + * + * @param Request $request A Request instance + * + * @return bool true if the Request is private, false otherwise + */ + private function isPrivateRequest(Request $request) + { + foreach ($this->options['private_headers'] as $key) { + $key = strtolower(str_replace('HTTP_', '', $key)); + + if ('cookie' === $key) { + if (count($request->cookies->all())) { + return true; + } + } elseif ($request->headers->has($key)) { + return true; + } + } + + return false; + } + + /** + * Records that an event took place. + * + * @param Request $request A Request instance + * @param string $event The event name + */ + private function record(Request $request, $event) + { + $this->traces[$this->getTraceKey($request)][] = $event; + } + + /** + * Calculates the key we use in the "trace" array for a given request. + * + * @param Request $request + * + * @return string + */ + private function getTraceKey(Request $request) + { + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + + return $request->getMethod().' '.$path; + } + + /** + * Checks whether the given (cached) response may be served as "stale" when a revalidation + * is currently in progress. + * + * @param Response $entry + * + * @return bool True when the stale response may be served, false otherwise. + */ + private function mayServeStaleWhileRevalidate(Response $entry) + { + $timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate'); + + if (null === $timeout) { + $timeout = $this->options['stale_while_revalidate']; + } + + return abs($entry->getTtl()) < $timeout; + } + + /** + * Waits for the store to release a locked entry. + * + * @param Request $request The request to wait for + * + * @return bool True if the lock was released before the internal timeout was hit; false if the wait timeout was exceeded. + */ + private function waitForLock(Request $request) + { + $wait = 0; + while ($this->store->isLocked($request) && $wait < 5000000) { + usleep(50000); + $wait += 50000; + } + + return $wait < 5000000; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php new file mode 100644 index 00000000..39a99e69 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php @@ -0,0 +1,93 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * ResponseCacheStrategy knows how to compute the Response cache HTTP header + * based on the different response cache headers. + * + * This implementation changes the master response TTL to the smallest TTL received + * or force validation if one of the surrogates has validation cache strategy. + * + * @author Fabien Potencier + */ +class ResponseCacheStrategy implements ResponseCacheStrategyInterface +{ + private $cacheable = true; + private $embeddedResponses = 0; + private $ttls = array(); + private $maxAges = array(); + private $isNotCacheableResponseEmbedded = false; + + /** + * {@inheritdoc} + */ + public function add(Response $response) + { + if ($response->isValidateable()) { + $this->cacheable = false; + } else { + $maxAge = $response->getMaxAge(); + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $maxAge; + + if (null === $maxAge) { + $this->isNotCacheableResponseEmbedded = true; + } + } + + ++$this->embeddedResponses; + } + + /** + * {@inheritdoc} + */ + public function update(Response $response) + { + // if we have no embedded Response, do nothing + if (0 === $this->embeddedResponses) { + return; + } + + // Remove validation related headers in order to avoid browsers using + // their own cache, because some of the response content comes from + // at least one embedded response (which likely has a different caching strategy). + if ($response->isValidateable()) { + $response->setEtag(null); + $response->setLastModified(null); + $this->cacheable = false; + } + + if (!$this->cacheable) { + $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); + + return; + } + + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $response->getMaxAge(); + + if ($this->isNotCacheableResponseEmbedded) { + $response->headers->removeCacheControlDirective('s-maxage'); + } elseif (null !== $maxAge = min($this->maxAges)) { + $response->setSharedMaxAge($maxAge); + $response->headers->set('Age', $maxAge - min($this->ttls)); + } + $response->setMaxAge(0); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php new file mode 100644 index 00000000..d70c2e06 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php @@ -0,0 +1,41 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * ResponseCacheStrategyInterface implementations know how to compute the + * Response cache HTTP header based on the different response cache headers. + * + * @author Fabien Potencier + */ +interface ResponseCacheStrategyInterface +{ + /** + * Adds a Response. + * + * @param Response $response + */ + public function add(Response $response); + + /** + * Updates the Response HTTP headers based on the embedded Responses. + * + * @param Response $response + */ + public function update(Response $response); +} diff --git a/vendor/symfony/http-kernel/HttpCache/Ssi.php b/vendor/symfony/http-kernel/HttpCache/Ssi.php new file mode 100644 index 00000000..3178c335 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Ssi.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Ssi implements the SSI capabilities to Request and Response instances. + * + * @author Sebastian Krebs + */ +class Ssi extends AbstractSurrogate +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ssi'; + } + + /** + * {@inheritdoc} + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), '', $uri); + } + + /** + * {@inheritdoc} + */ + public function process(Request $request, Response $response) + { + $type = $response->headers->get('Content-Type'); + if (empty($type)) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!in_array($parts[0], $this->contentTypes)) { + return $response; + } + + // we don't use a proper XML parser here as we can have SSI tags in a plain text response + $content = $response->getContent(); + + $chunks = preg_split('##', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); + + $i = 1; + while (isset($chunks[$i])) { + $options = array(); + preg_match_all('/(virtual)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['virtual'])) { + throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.'); + } + + $chunks[$i] = sprintf('surrogate->handle($this, %s, \'\', false) ?>'."\n", + var_export($options['virtual'], true) + ); + ++$i; + $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); + ++$i; + } + $content = implode('', $chunks); + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'SSI'); + + // remove SSI/1.0 from the Surrogate-Control header + $this->removeFromControl($response); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Store.php b/vendor/symfony/http-kernel/HttpCache/Store.php new file mode 100644 index 00000000..c4d961e6 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Store.php @@ -0,0 +1,508 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Store implements all the logic for storing cache metadata (Request and Response headers). + * + * @author Fabien Potencier + */ +class Store implements StoreInterface +{ + protected $root; + private $keyCache; + private $locks; + + /** + * Constructor. + * + * @param string $root The path to the cache directory + * + * @throws \RuntimeException + */ + public function __construct($root) + { + $this->root = $root; + if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { + throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); + } + $this->keyCache = new \SplObjectStorage(); + $this->locks = array(); + } + + /** + * Cleanups storage. + */ + public function cleanup() + { + // unlock everything + foreach ($this->locks as $lock) { + flock($lock, LOCK_UN); + fclose($lock); + } + + $this->locks = array(); + } + + /** + * Tries to lock the cache for a given Request, without blocking. + * + * @param Request $request A Request instance + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request) + { + $key = $this->getCacheKey($request); + + if (!isset($this->locks[$key])) { + $path = $this->getPath($key); + if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) { + return $path; + } + $h = fopen($path, 'cb'); + if (!flock($h, LOCK_EX | LOCK_NB)) { + fclose($h); + + return $path; + } + + $this->locks[$key] = $h; + } + + return true; + } + + /** + * Releases the lock for the given Request. + * + * @param Request $request A Request instance + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + + return true; + } + + return false; + } + + public function isLocked(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + return true; // shortcut if lock held by this process + } + + if (!file_exists($path = $this->getPath($key))) { + return false; + } + + $h = fopen($path, 'rb'); + flock($h, LOCK_EX | LOCK_NB, $wouldBlock); + flock($h, LOCK_UN); // release the lock we just acquired + fclose($h); + + return (bool) $wouldBlock; + } + + /** + * Locates a cached Response for the Request provided. + * + * @param Request $request A Request instance + * + * @return Response|null A Response instance, or null if no cache entry was found + */ + public function lookup(Request $request) + { + $key = $this->getCacheKey($request); + + if (!$entries = $this->getMetadata($key)) { + return; + } + + // find a cached entry that matches the request. + $match = null; + foreach ($entries as $entry) { + if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { + $match = $entry; + + break; + } + } + + if (null === $match) { + return; + } + + list($req, $headers) = $match; + if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) { + return $this->restoreResponse($headers, $body); + } + + // TODO the metaStore referenced an entity that doesn't exist in + // the entityStore. We definitely want to return nil but we should + // also purge the entry from the meta-store when this is detected. + } + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return string The key under which the response is stored + * + * @throws \RuntimeException + */ + public function write(Request $request, Response $response) + { + $key = $this->getCacheKey($request); + $storedEnv = $this->persistRequest($request); + + // write the response body to the entity store if this is the original response + if (!$response->headers->has('X-Content-Digest')) { + $digest = $this->generateContentDigest($response); + + if (false === $this->save($digest, $response->getContent())) { + throw new \RuntimeException('Unable to store the entity.'); + } + + $response->headers->set('X-Content-Digest', $digest); + + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', strlen($response->getContent())); + } + } + + // read existing cache entries, remove non-varying, and add this one to the list + $entries = array(); + $vary = $response->headers->get('vary'); + foreach ($this->getMetadata($key) as $entry) { + if (!isset($entry[1]['vary'][0])) { + $entry[1]['vary'] = array(''); + } + + if ($vary != $entry[1]['vary'][0] || !$this->requestsMatch($vary, $entry[0], $storedEnv)) { + $entries[] = $entry; + } + } + + $headers = $this->persistResponse($response); + unset($headers['age']); + + array_unshift($entries, array($storedEnv, $headers)); + + if (false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + + return $key; + } + + /** + * Returns content digest for $response. + * + * @param Response $response + * + * @return string + */ + protected function generateContentDigest(Response $response) + { + return 'en'.hash('sha256', $response->getContent()); + } + + /** + * Invalidates all cache entries that match the request. + * + * @param Request $request A Request instance + * + * @throws \RuntimeException + */ + public function invalidate(Request $request) + { + $modified = false; + $key = $this->getCacheKey($request); + + $entries = array(); + foreach ($this->getMetadata($key) as $entry) { + $response = $this->restoreResponse($entry[1]); + if ($response->isFresh()) { + $response->expire(); + $modified = true; + $entries[] = array($entry[0], $this->persistResponse($response)); + } else { + $entries[] = $entry; + } + } + + if ($modified && false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + } + + /** + * Determines whether two Request HTTP header sets are non-varying based on + * the vary response header value provided. + * + * @param string $vary A Response vary header + * @param array $env1 A Request HTTP header array + * @param array $env2 A Request HTTP header array + * + * @return bool true if the two environments match, false otherwise + */ + private function requestsMatch($vary, $env1, $env2) + { + if (empty($vary)) { + return true; + } + + foreach (preg_split('/[\s,]+/', $vary) as $header) { + $key = str_replace('_', '-', strtolower($header)); + $v1 = isset($env1[$key]) ? $env1[$key] : null; + $v2 = isset($env2[$key]) ? $env2[$key] : null; + if ($v1 !== $v2) { + return false; + } + } + + return true; + } + + /** + * Gets all data associated with the given key. + * + * Use this method only if you know what you are doing. + * + * @param string $key The store key + * + * @return array An array of data associated with the key + */ + private function getMetadata($key) + { + if (!$entries = $this->load($key)) { + return array(); + } + + return unserialize($entries); + } + + /** + * Purges data for the given URL. + * + * This method purges both the HTTP and the HTTPS version of the cache entry. + * + * @param string $url A URL + * + * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise + */ + public function purge($url) + { + $http = preg_replace('#^https:#', 'http:', $url); + $https = preg_replace('#^http:#', 'https:', $url); + + $purgedHttp = $this->doPurge($http); + $purgedHttps = $this->doPurge($https); + + return $purgedHttp || $purgedHttps; + } + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + private function doPurge($url) + { + $key = $this->getCacheKey(Request::create($url)); + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + } + + if (file_exists($path = $this->getPath($key))) { + unlink($path); + + return true; + } + + return false; + } + + /** + * Loads data for the given key. + * + * @param string $key The store key + * + * @return string The data associated with the key + */ + private function load($key) + { + $path = $this->getPath($key); + + return file_exists($path) ? file_get_contents($path) : false; + } + + /** + * Save data for the given key. + * + * @param string $key The store key + * @param string $data The data to store + * + * @return bool + */ + private function save($key, $data) + { + $path = $this->getPath($key); + + if (isset($this->locks[$key])) { + $fp = $this->locks[$key]; + @ftruncate($fp, 0); + @fseek($fp, 0); + $len = @fwrite($fp, $data); + if (strlen($data) !== $len) { + @ftruncate($fp, 0); + + return false; + } + } else { + if (!file_exists(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) { + return false; + } + + $tmpFile = tempnam(dirname($path), basename($path)); + if (false === $fp = @fopen($tmpFile, 'wb')) { + return false; + } + @fwrite($fp, $data); + @fclose($fp); + + if ($data != file_get_contents($tmpFile)) { + return false; + } + + if (false === @rename($tmpFile, $path)) { + return false; + } + } + + @chmod($path, 0666 & ~umask()); + } + + public function getPath($key) + { + return $this->root.DIRECTORY_SEPARATOR.substr($key, 0, 2).DIRECTORY_SEPARATOR.substr($key, 2, 2).DIRECTORY_SEPARATOR.substr($key, 4, 2).DIRECTORY_SEPARATOR.substr($key, 6); + } + + /** + * Generates a cache key for the given Request. + * + * This method should return a key that must only depend on a + * normalized version of the request URI. + * + * If the same URI can have more than one representation, based on some + * headers, use a Vary header to indicate them, and each representation will + * be stored independently under the same cache key. + * + * @param Request $request A Request instance + * + * @return string A key for the given Request + */ + protected function generateCacheKey(Request $request) + { + return 'md'.hash('sha256', $request->getUri()); + } + + /** + * Returns a cache key for the given Request. + * + * @param Request $request A Request instance + * + * @return string A key for the given Request + */ + private function getCacheKey(Request $request) + { + if (isset($this->keyCache[$request])) { + return $this->keyCache[$request]; + } + + return $this->keyCache[$request] = $this->generateCacheKey($request); + } + + /** + * Persists the Request HTTP headers. + * + * @param Request $request A Request instance + * + * @return array An array of HTTP headers + */ + private function persistRequest(Request $request) + { + return $request->headers->all(); + } + + /** + * Persists the Response HTTP headers. + * + * @param Response $response A Response instance + * + * @return array An array of HTTP headers + */ + private function persistResponse(Response $response) + { + $headers = $response->headers->all(); + $headers['X-Status'] = array($response->getStatusCode()); + + return $headers; + } + + /** + * Restores a Response from the HTTP headers and body. + * + * @param array $headers An array of HTTP headers for the Response + * @param string $body The Response body + * + * @return Response + */ + private function restoreResponse($headers, $body = null) + { + $status = $headers['X-Status'][0]; + unset($headers['X-Status']); + + if (null !== $body) { + $headers['X-Body-File'] = array($body); + } + + return new Response($body, $status, $headers); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/StoreInterface.php b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php new file mode 100644 index 00000000..ddc0c04e --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php @@ -0,0 +1,96 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Interface implemented by HTTP cache stores. + * + * @author Fabien Potencier + */ +interface StoreInterface +{ + /** + * Locates a cached Response for the Request provided. + * + * @param Request $request A Request instance + * + * @return Response|null A Response instance, or null if no cache entry was found + */ + public function lookup(Request $request); + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return string The key under which the response is stored + */ + public function write(Request $request, Response $response); + + /** + * Invalidates all cache entries that match the request. + * + * @param Request $request A Request instance + */ + public function invalidate(Request $request); + + /** + * Locks the cache for a given Request. + * + * @param Request $request A Request instance + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request); + + /** + * Releases the lock for the given Request. + * + * @param Request $request A Request instance + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request); + + /** + * Returns whether or not a lock exists. + * + * @param Request $request A Request instance + * + * @return bool true if lock exists, false otherwise + */ + public function isLocked(Request $request); + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + public function purge($url); + + /** + * Cleanups storage. + */ + public function cleanup(); +} diff --git a/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php new file mode 100644 index 00000000..5d65fd65 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.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\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +interface SurrogateInterface +{ + /** + * Returns surrogate name. + * + * @return string + */ + public function getName(); + + /** + * Returns a new cache strategy instance. + * + * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + */ + public function createCacheStrategy(); + + /** + * Checks that at least one surrogate has Surrogate capability. + * + * @param Request $request A Request instance + * + * @return bool true if one surrogate has Surrogate capability, false otherwise + */ + public function hasSurrogateCapability(Request $request); + + /** + * Adds Surrogate-capability to the given Request. + * + * @param Request $request A Request instance + */ + public function addSurrogateCapability(Request $request); + + /** + * Adds HTTP headers to specify that the Response needs to be parsed for Surrogate. + * + * This method only adds an Surrogate HTTP header if the Response has some Surrogate tags. + * + * @param Response $response A Response instance + */ + public function addSurrogateControl(Response $response); + + /** + * Checks that the Response needs to be parsed for Surrogate tags. + * + * @param Response $response A Response instance + * + * @return bool true if the Response needs to be parsed, false otherwise + */ + public function needsParsing(Response $response); + + /** + * Renders a Surrogate tag. + * + * @param string $uri A URI + * @param string $alt An alternate URI + * @param bool $ignoreErrors Whether to ignore errors or not + * @param string $comment A comment to add as an esi:include tag + * + * @return string + */ + public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = ''); + + /** + * Replaces a Response Surrogate tags with the included resource content. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return Response + */ + public function process(Request $request, Response $response); + + /** + * Handles a Surrogate from the cache. + * + * @param HttpCache $cache An HttpCache instance + * @param string $uri The main URI + * @param string $alt An alternative URI + * @param bool $ignoreErrors Whether to ignore errors or not + * + * @return string + * + * @throws \RuntimeException + * @throws \Exception + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors); +} diff --git a/vendor/symfony/http-kernel/HttpKernel.php b/vendor/symfony/http-kernel/HttpKernel.php new file mode 100644 index 00000000..8d55ccde --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernel.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * HttpKernel notifies events to convert a Request object to a Response one. + * + * @author Fabien Potencier + */ +class HttpKernel implements HttpKernelInterface, TerminableInterface +{ + protected $dispatcher; + protected $resolver; + protected $requestStack; + private $argumentResolver; + + public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null, ArgumentResolverInterface $argumentResolver = null) + { + $this->dispatcher = $dispatcher; + $this->resolver = $resolver; + $this->requestStack = $requestStack ?: new RequestStack(); + $this->argumentResolver = $argumentResolver; + + if (null === $this->argumentResolver) { + @trigger_error(sprintf('As of 3.1 an %s is used to resolve arguments. In 4.0 the $argumentResolver becomes the %s if no other is provided instead of using the $resolver argument.', ArgumentResolverInterface::class, ArgumentResolver::class), E_USER_DEPRECATED); + // fallback in case of deprecations + $this->argumentResolver = $resolver; + } + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $request->headers->set('X-Php-Ob-Level', ob_get_level()); + + try { + return $this->handleRaw($request, $type); + } catch (\Exception $e) { + if ($e instanceof RequestExceptionInterface) { + $e = new BadRequestHttpException($e->getMessage(), $e); + } + if (false === $catch) { + $this->finishRequest($request, $type); + + throw $e; + } + + return $this->handleException($e, $request, $type); + } + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + $this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response)); + } + + /** + * @throws \LogicException If the request stack is empty + * + * @internal + */ + public function terminateWithException(\Exception $exception) + { + if (!$request = $this->requestStack->getMasterRequest()) { + throw new \LogicException('Request stack is empty', 0, $exception); + } + + $response = $this->handleException($exception, $request, self::MASTER_REQUEST); + + $response->sendHeaders(); + $response->sendContent(); + + $this->terminate($request, $response); + } + + /** + * Handles a request to convert it to a response. + * + * Exceptions are not caught. + * + * @param Request $request A Request instance + * @param int $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * + * @return Response A Response instance + * + * @throws \LogicException If one of the listener does not behave as expected + * @throws NotFoundHttpException When controller cannot be found + */ + private function handleRaw(Request $request, $type = self::MASTER_REQUEST) + { + $this->requestStack->push($request); + + // request + $event = new GetResponseEvent($this, $request, $type); + $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); + + if ($event->hasResponse()) { + return $this->filterResponse($event->getResponse(), $request, $type); + } + + // load controller + if (false === $controller = $this->resolver->getController($request)) { + throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); + } + + $event = new FilterControllerEvent($this, $controller, $request, $type); + $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); + $controller = $event->getController(); + + // controller arguments + $arguments = $this->argumentResolver->getArguments($request, $controller); + + $event = new FilterControllerArgumentsEvent($this, $controller, $arguments, $request, $type); + $this->dispatcher->dispatch(KernelEvents::CONTROLLER_ARGUMENTS, $event); + $controller = $event->getController(); + $arguments = $event->getArguments(); + + // call controller + $response = call_user_func_array($controller, $arguments); + + // view + if (!$response instanceof Response) { + $event = new GetResponseForControllerResultEvent($this, $request, $type, $response); + $this->dispatcher->dispatch(KernelEvents::VIEW, $event); + + if ($event->hasResponse()) { + $response = $event->getResponse(); + } + + if (!$response instanceof Response) { + $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response)); + + // the user may have forgotten to return something + if (null === $response) { + $msg .= ' Did you forget to add a return statement somewhere in your controller?'; + } + throw new \LogicException($msg); + } + } + + return $this->filterResponse($response, $request, $type); + } + + /** + * Filters a response object. + * + * @param Response $response A Response instance + * @param Request $request An error message in case the response is not a Response object + * @param int $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * + * @return Response The filtered Response instance + * + * @throws \RuntimeException if the passed object is not a Response instance + */ + private function filterResponse(Response $response, Request $request, $type) + { + $event = new FilterResponseEvent($this, $request, $type, $response); + + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->finishRequest($request, $type); + + return $event->getResponse(); + } + + /** + * Publishes the finish request event, then pop the request from the stack. + * + * Note that the order of the operations is important here, otherwise + * operations such as {@link RequestStack::getParentRequest()} can lead to + * weird results. + * + * @param Request $request + * @param int $type + */ + private function finishRequest(Request $request, $type) + { + $this->dispatcher->dispatch(KernelEvents::FINISH_REQUEST, new FinishRequestEvent($this, $request, $type)); + $this->requestStack->pop(); + } + + /** + * Handles an exception by trying to convert it to a Response. + * + * @param \Exception $e An \Exception instance + * @param Request $request A Request instance + * @param int $type The type of the request + * + * @return Response A Response instance + * + * @throws \Exception + */ + private function handleException(\Exception $e, $request, $type) + { + $event = new GetResponseForExceptionEvent($this, $request, $type, $e); + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + + // a listener might have replaced the exception + $e = $event->getException(); + + if (!$event->hasResponse()) { + $this->finishRequest($request, $type); + + throw $e; + } + + $response = $event->getResponse(); + + // the developer asked for a specific status code + if ($response->headers->has('X-Status-Code')) { + @trigger_error(sprintf('Using the X-Status-Code header is deprecated since version 3.3 and will be removed in 4.0. Use %s::allowCustomResponseCode() instead.', GetResponseForExceptionEvent::class), E_USER_DEPRECATED); + + $response->setStatusCode($response->headers->get('X-Status-Code')); + + $response->headers->remove('X-Status-Code'); + } elseif (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) { + // ensure that we actually have an error response + if ($e instanceof HttpExceptionInterface) { + // keep the HTTP status code and headers + $response->setStatusCode($e->getStatusCode()); + $response->headers->add($e->getHeaders()); + } else { + $response->setStatusCode(500); + } + } + + try { + return $this->filterResponse($response, $request, $type); + } catch (\Exception $e) { + return $response; + } + } + + private function varToString($var) + { + if (is_object($var)) { + return sprintf('Object(%s)', get_class($var)); + } + + if (is_array($var)) { + $a = array(); + foreach ($var as $k => $v) { + $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + } + + return sprintf('Array(%s)', implode(', ', $a)); + } + + if (is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'false'; + } + + if (true === $var) { + return 'true'; + } + + return (string) $var; + } +} diff --git a/vendor/symfony/http-kernel/HttpKernelInterface.php b/vendor/symfony/http-kernel/HttpKernelInterface.php new file mode 100644 index 00000000..5050bfcf --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernelInterface.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\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * HttpKernelInterface handles a Request to convert it to a Response. + * + * @author Fabien Potencier + */ +interface HttpKernelInterface +{ + const MASTER_REQUEST = 1; + const SUB_REQUEST = 2; + + /** + * Handles a Request to convert it to a Response. + * + * When $catch is true, the implementation must catch all exceptions + * and do its best to convert them to a Response instance. + * + * @param Request $request A Request instance + * @param int $type The type of the request + * (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * @param bool $catch Whether to catch exceptions or not + * + * @return Response A Response instance + * + * @throws \Exception When an Exception occurs during processing + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); +} diff --git a/vendor/symfony/http-kernel/Kernel.php b/vendor/symfony/http-kernel/Kernel.php new file mode 100644 index 00000000..0cab7988 --- /dev/null +++ b/vendor/symfony/http-kernel/Kernel.php @@ -0,0 +1,849 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\DependencyInjection\Loader\IniFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; +use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; +use Symfony\Component\HttpKernel\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; +use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; +use Symfony\Component\Config\Loader\GlobFileLoader; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Config\Loader\DelegatingLoader; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\ClassLoader\ClassCollectionLoader; + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of bundles. + * + * @author Fabien Potencier + */ +abstract class Kernel implements KernelInterface, TerminableInterface +{ + /** + * @var BundleInterface[] + */ + protected $bundles = array(); + + protected $bundleMap; + protected $container; + protected $rootDir; + protected $environment; + protected $debug; + protected $booted = false; + protected $name; + protected $startTime; + protected $loadClassCache; + + private $projectDir; + + const VERSION = '3.3.2'; + const VERSION_ID = 30302; + const MAJOR_VERSION = 3; + const MINOR_VERSION = 3; + const RELEASE_VERSION = 2; + const EXTRA_VERSION = ''; + + const END_OF_MAINTENANCE = '01/2018'; + const END_OF_LIFE = '07/2018'; + + /** + * Constructor. + * + * @param string $environment The environment + * @param bool $debug Whether to enable debugging or not + */ + public function __construct($environment, $debug) + { + $this->environment = $environment; + $this->debug = (bool) $debug; + $this->rootDir = $this->getRootDir(); + $this->name = $this->getName(); + + if ($this->debug) { + $this->startTime = microtime(true); + } + } + + public function __clone() + { + if ($this->debug) { + $this->startTime = microtime(true); + } + + $this->booted = false; + $this->container = null; + } + + /** + * Boots the current kernel. + */ + public function boot() + { + if (true === $this->booted) { + return; + } + + if ($this->loadClassCache) { + $this->doLoadClassCache($this->loadClassCache[0], $this->loadClassCache[1]); + } + + // init bundles + $this->initializeBundles(); + + // init container + $this->initializeContainer(); + + foreach ($this->getBundles() as $bundle) { + $bundle->setContainer($this->container); + $bundle->boot(); + } + + $this->booted = true; + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + if (false === $this->booted) { + return; + } + + if ($this->getHttpKernel() instanceof TerminableInterface) { + $this->getHttpKernel()->terminate($request, $response); + } + } + + /** + * {@inheritdoc} + */ + public function shutdown() + { + if (false === $this->booted) { + return; + } + + $this->booted = false; + + foreach ($this->getBundles() as $bundle) { + $bundle->shutdown(); + $bundle->setContainer(null); + } + + $this->container = null; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + if (false === $this->booted) { + $this->boot(); + } + + return $this->getHttpKernel()->handle($request, $type, $catch); + } + + /** + * Gets a HTTP kernel from the container. + * + * @return HttpKernel + */ + protected function getHttpKernel() + { + return $this->container->get('http_kernel'); + } + + /** + * {@inheritdoc} + */ + public function getBundles() + { + return $this->bundles; + } + + /** + * {@inheritdoc} + */ + public function getBundle($name, $first = true) + { + if (!isset($this->bundleMap[$name])) { + throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the registerBundles() method of your %s.php file?', $name, get_class($this))); + } + + if (true === $first) { + return $this->bundleMap[$name][0]; + } + + return $this->bundleMap[$name]; + } + + /** + * {@inheritdoc} + * + * @throws \RuntimeException if a custom resource is hidden by a resource in a derived bundle + */ + public function locateResource($name, $dir = null, $first = true) + { + if ('@' !== $name[0]) { + throw new \InvalidArgumentException(sprintf('A resource name must start with @ ("%s" given).', $name)); + } + + if (false !== strpos($name, '..')) { + throw new \RuntimeException(sprintf('File name "%s" contains invalid characters (..).', $name)); + } + + $bundleName = substr($name, 1); + $path = ''; + if (false !== strpos($bundleName, '/')) { + list($bundleName, $path) = explode('/', $bundleName, 2); + } + + $isResource = 0 === strpos($path, 'Resources') && null !== $dir; + $overridePath = substr($path, 9); + $resourceBundle = null; + $bundles = $this->getBundle($bundleName, false); + $files = array(); + + foreach ($bundles as $bundle) { + if ($isResource && file_exists($file = $dir.'/'.$bundle->getName().$overridePath)) { + if (null !== $resourceBundle) { + throw new \RuntimeException(sprintf('"%s" resource is hidden by a resource from the "%s" derived bundle. Create a "%s" file to override the bundle resource.', + $file, + $resourceBundle, + $dir.'/'.$bundles[0]->getName().$overridePath + )); + } + + if ($first) { + return $file; + } + $files[] = $file; + } + + if (file_exists($file = $bundle->getPath().'/'.$path)) { + if ($first && !$isResource) { + return $file; + } + $files[] = $file; + $resourceBundle = $bundle->getName(); + } + } + + if (count($files) > 0) { + return $first && $isResource ? $files[0] : $files; + } + + throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $name)); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + if (null === $this->name) { + $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir)); + if (ctype_digit($this->name[0])) { + $this->name = '_'.$this->name; + } + } + + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return $this->debug; + } + + /** + * {@inheritdoc} + */ + public function getRootDir() + { + if (null === $this->rootDir) { + $r = new \ReflectionObject($this); + $this->rootDir = dirname($r->getFileName()); + } + + return $this->rootDir; + } + + /** + * Gets the application root dir (path of the project's composer file). + * + * @return string The project root dir + */ + public function getProjectDir() + { + if (null === $this->projectDir) { + $r = new \ReflectionObject($this); + $dir = $rootDir = dirname($r->getFileName()); + while (!file_exists($dir.'/composer.json')) { + if ($dir === dirname($dir)) { + return $this->projectDir = $rootDir; + } + $dir = dirname($dir); + } + $this->projectDir = $dir; + } + + return $this->projectDir; + } + + /** + * {@inheritdoc} + */ + public function getContainer() + { + return $this->container; + } + + /** + * Loads the PHP class cache. + * + * This methods only registers the fact that you want to load the cache classes. + * The cache will actually only be loaded when the Kernel is booted. + * + * That optimization is mainly useful when using the HttpCache class in which + * case the class cache is not loaded if the Response is in the cache. + * + * @param string $name The cache name prefix + * @param string $extension File extension of the resulting file + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function loadClassCache($name = 'classes', $extension = '.php') + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + $this->loadClassCache = array($name, $extension); + } + + /** + * @internal + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function setClassCache(array $classes) + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + file_put_contents($this->getCacheDir().'/classes.map', sprintf('getCacheDir().'/annotations.map', sprintf('debug ? $this->startTime : -INF; + } + + /** + * {@inheritdoc} + */ + public function getCacheDir() + { + return $this->rootDir.'/cache/'.$this->environment; + } + + /** + * {@inheritdoc} + */ + public function getLogDir() + { + return $this->rootDir.'/logs'; + } + + /** + * {@inheritdoc} + */ + public function getCharset() + { + return 'UTF-8'; + } + + /** + * @deprecated since version 3.3, to be removed in 4.0. + */ + protected function doLoadClassCache($name, $extension) + { + if (\PHP_VERSION_ID >= 70000) { + @trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED); + } + + if (!$this->booted && is_file($this->getCacheDir().'/classes.map')) { + ClassCollectionLoader::load(include($this->getCacheDir().'/classes.map'), $this->getCacheDir(), $name, $this->debug, false, $extension); + } + } + + /** + * Initializes the data structures related to the bundle management. + * + * - the bundles property maps a bundle name to the bundle instance, + * - the bundleMap property maps a bundle name to the bundle inheritance hierarchy (most derived bundle first). + * + * @throws \LogicException if two bundles share a common name + * @throws \LogicException if a bundle tries to extend a non-registered bundle + * @throws \LogicException if a bundle tries to extend itself + * @throws \LogicException if two bundles extend the same ancestor + */ + protected function initializeBundles() + { + // init bundles + $this->bundles = array(); + $topMostBundles = array(); + $directChildren = array(); + + foreach ($this->registerBundles() as $bundle) { + $name = $bundle->getName(); + if (isset($this->bundles[$name])) { + throw new \LogicException(sprintf('Trying to register two bundles with the same name "%s"', $name)); + } + $this->bundles[$name] = $bundle; + + if ($parentName = $bundle->getParent()) { + if (isset($directChildren[$parentName])) { + throw new \LogicException(sprintf('Bundle "%s" is directly extended by two bundles "%s" and "%s".', $parentName, $name, $directChildren[$parentName])); + } + if ($parentName == $name) { + throw new \LogicException(sprintf('Bundle "%s" can not extend itself.', $name)); + } + $directChildren[$parentName] = $name; + } else { + $topMostBundles[$name] = $bundle; + } + } + + // look for orphans + if (!empty($directChildren) && count($diff = array_diff_key($directChildren, $this->bundles))) { + $diff = array_keys($diff); + + throw new \LogicException(sprintf('Bundle "%s" extends bundle "%s", which is not registered.', $directChildren[$diff[0]], $diff[0])); + } + + // inheritance + $this->bundleMap = array(); + foreach ($topMostBundles as $name => $bundle) { + $bundleMap = array($bundle); + $hierarchy = array($name); + + while (isset($directChildren[$name])) { + $name = $directChildren[$name]; + array_unshift($bundleMap, $this->bundles[$name]); + $hierarchy[] = $name; + } + + foreach ($hierarchy as $hierarchyBundle) { + $this->bundleMap[$hierarchyBundle] = $bundleMap; + array_pop($bundleMap); + } + } + } + + /** + * The extension point similar to the Bundle::build() method. + * + * Use this method to register compiler passes and manipulate the container during the building process. + * + * @param ContainerBuilder $container + */ + protected function build(ContainerBuilder $container) + { + } + + /** + * Gets the container class. + * + * @return string The container class + */ + protected function getContainerClass() + { + return $this->name.ucfirst($this->environment).($this->debug ? 'Debug' : '').'ProjectContainer'; + } + + /** + * Gets the container's base class. + * + * All names except Container must be fully qualified. + * + * @return string + */ + protected function getContainerBaseClass() + { + return 'Container'; + } + + /** + * Initializes the service container. + * + * The cached version of the service container is used when fresh, otherwise the + * container is built. + */ + protected function initializeContainer() + { + $class = $this->getContainerClass(); + $cache = new ConfigCache($this->getCacheDir().'/'.$class.'.php', $this->debug); + $fresh = true; + if (!$cache->isFresh()) { + if ($this->debug) { + $collectedLogs = array(); + $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) { + return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; + } + + $collectedLogs[] = array( + 'type' => $type, + 'message' => $message, + 'file' => $file, + 'line' => $line, + ); + }); + } + + try { + $container = null; + $container = $this->buildContainer(); + $container->compile(); + } finally { + if ($this->debug) { + restore_error_handler(); + + file_put_contents($this->getCacheDir().'/'.$class.'Deprecations.log', serialize($collectedLogs)); + file_put_contents($this->getCacheDir().'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : ''); + } + } + $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); + + $fresh = false; + } + + require_once $cache->getPath(); + + $this->container = new $class(); + $this->container->set('kernel', $this); + + if (!$fresh && $this->container->has('cache_warmer')) { + $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir')); + } + } + + /** + * Returns the kernel parameters. + * + * @return array An array of kernel parameters + */ + protected function getKernelParameters() + { + $bundles = array(); + $bundlesMetadata = array(); + + foreach ($this->bundles as $name => $bundle) { + $bundles[$name] = get_class($bundle); + $bundlesMetadata[$name] = array( + 'parent' => $bundle->getParent(), + 'path' => $bundle->getPath(), + 'namespace' => $bundle->getNamespace(), + ); + } + + return array_merge( + array( + 'kernel.root_dir' => realpath($this->rootDir) ?: $this->rootDir, + 'kernel.project_dir' => realpath($this->getProjectDir()) ?: $this->getProjectDir(), + 'kernel.environment' => $this->environment, + 'kernel.debug' => $this->debug, + 'kernel.name' => $this->name, + 'kernel.cache_dir' => realpath($this->getCacheDir()) ?: $this->getCacheDir(), + 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), + 'kernel.bundles' => $bundles, + 'kernel.bundles_metadata' => $bundlesMetadata, + 'kernel.charset' => $this->getCharset(), + 'kernel.container_class' => $this->getContainerClass(), + ), + $this->getEnvParameters(false) + ); + } + + /** + * Gets the environment parameters. + * + * Only the parameters starting with "SYMFONY__" are considered. + * + * @return array An array of parameters + * + * @deprecated since version 3.3, to be removed in 4.0 + */ + protected function getEnvParameters() + { + if (0 === func_num_args() || func_get_arg(0)) { + @trigger_error(sprintf('The %s() method is deprecated as of 3.3 and will be removed in 4.0. Use the %%env()%% syntax to get the value of any environment variable from configuration files instead.', __METHOD__), E_USER_DEPRECATED); + } + + $parameters = array(); + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'SYMFONY__')) { + @trigger_error(sprintf('The support of special environment variables that start with SYMFONY__ (such as "%s") is deprecated as of 3.3 and will be removed in 4.0. Use the %%env()%% syntax instead to get the value of environment variables in configuration files.', $key), E_USER_DEPRECATED); + $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value; + } + } + + return $parameters; + } + + /** + * Builds the service container. + * + * @return ContainerBuilder The compiled service container + * + * @throws \RuntimeException + */ + protected function buildContainer() + { + foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) { + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir)); + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(sprintf("Unable to write in the %s directory (%s)\n", $name, $dir)); + } + } + + $container = $this->getContainerBuilder(); + $container->addObjectResource($this); + $this->prepareContainer($container); + + if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { + $container->merge($cont); + } + + $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this)); + $container->addResource(new EnvParametersResource('SYMFONY__')); + + return $container; + } + + /** + * Prepares the ContainerBuilder before it is compiled. + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function prepareContainer(ContainerBuilder $container) + { + $extensions = array(); + foreach ($this->bundles as $bundle) { + if ($extension = $bundle->getContainerExtension()) { + $container->registerExtension($extension); + $extensions[] = $extension->getAlias(); + } + + if ($this->debug) { + $container->addObjectResource($bundle); + } + } + + foreach ($this->bundles as $bundle) { + $bundle->build($container); + } + + $this->build($container); + + // ensure these extensions are implicitly loaded + $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions)); + } + + /** + * Gets a new ContainerBuilder instance used to build the service container. + * + * @return ContainerBuilder + */ + protected function getContainerBuilder() + { + $container = new ContainerBuilder(); + $container->getParameterBag()->add($this->getKernelParameters()); + + if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator')) { + $container->setProxyInstantiator(new RuntimeInstantiator()); + } + + return $container; + } + + /** + * Dumps the service container to PHP code in the cache. + * + * @param ConfigCache $cache The config cache + * @param ContainerBuilder $container The service container + * @param string $class The name of the class to generate + * @param string $baseClass The name of the container's base class + */ + protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, $class, $baseClass) + { + // cache the container + $dumper = new PhpDumper($container); + + if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper')) { + $dumper->setProxyDumper(new ProxyDumper(md5($cache->getPath()))); + } + + $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass, 'file' => $cache->getPath(), 'debug' => $this->debug)); + + $cache->write($content, $container->getResources()); + } + + /** + * Returns a loader for the container. + * + * @param ContainerInterface $container The service container + * + * @return DelegatingLoader The loader + */ + protected function getContainerLoader(ContainerInterface $container) + { + $locator = new FileLocator($this); + $resolver = new LoaderResolver(array( + new XmlFileLoader($container, $locator), + new YamlFileLoader($container, $locator), + new IniFileLoader($container, $locator), + new PhpFileLoader($container, $locator), + new GlobFileLoader($locator), + new DirectoryLoader($container, $locator), + new ClosureLoader($container), + )); + + return new DelegatingLoader($resolver); + } + + /** + * Removes comments from a PHP source string. + * + * We don't use the PHP php_strip_whitespace() function + * as we want the content to be readable and well-formatted. + * + * @param string $source A PHP string + * + * @return string The PHP string with the comments removed + */ + public static function stripComments($source) + { + if (!function_exists('token_get_all')) { + return $source; + } + + $rawChunk = ''; + $output = ''; + $tokens = token_get_all($source); + $ignoreSpace = false; + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + if (!isset($token[1]) || 'b"' === $token) { + $rawChunk .= $token; + } elseif (T_START_HEREDOC === $token[0]) { + $output .= $rawChunk.$token[1]; + do { + $token = $tokens[++$i]; + $output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; + } while ($token[0] !== T_END_HEREDOC); + $rawChunk = ''; + } elseif (T_WHITESPACE === $token[0]) { + if ($ignoreSpace) { + $ignoreSpace = false; + + continue; + } + + // replace multiple new lines with a single newline + $rawChunk .= preg_replace(array('/\n{2,}/S'), "\n", $token[1]); + } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { + $ignoreSpace = true; + } else { + $rawChunk .= $token[1]; + + // The PHP-open tag already has a new-line + if (T_OPEN_TAG === $token[0]) { + $ignoreSpace = true; + } + } + } + + $output .= $rawChunk; + + if (\PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + unset($tokens, $rawChunk); + gc_mem_caches(); + } + + return $output; + } + + public function serialize() + { + return serialize(array($this->environment, $this->debug)); + } + + public function unserialize($data) + { + if (\PHP_VERSION_ID >= 70000) { + list($environment, $debug) = unserialize($data, array('allowed_classes' => false)); + } else { + list($environment, $debug) = unserialize($data); + } + + $this->__construct($environment, $debug); + } +} diff --git a/vendor/symfony/http-kernel/KernelEvents.php b/vendor/symfony/http-kernel/KernelEvents.php new file mode 100644 index 00000000..3e961737 --- /dev/null +++ b/vendor/symfony/http-kernel/KernelEvents.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\HttpKernel; + +/** + * Contains all events thrown in the HttpKernel component. + * + * @author Bernhard Schussek + */ +final class KernelEvents +{ + /** + * The REQUEST event occurs at the very beginning of request + * dispatching. + * + * This event allows you to create a response for a request before any + * other code in the framework is executed. + * + * @Event("Symfony\Component\HttpKernel\Event\GetResponseEvent") + * + * @var string + */ + const REQUEST = 'kernel.request'; + + /** + * The EXCEPTION event occurs when an uncaught exception appears. + * + * This event allows you to create a response for a thrown exception or + * to modify the thrown exception. + * + * @Event("Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent") + * + * @var string + */ + const EXCEPTION = 'kernel.exception'; + + /** + * The VIEW event occurs when the return value of a controller + * is not a Response instance. + * + * This event allows you to create a response for the return value of the + * controller. + * + * @Event("Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent") + * + * @var string + */ + const VIEW = 'kernel.view'; + + /** + * The CONTROLLER event occurs once a controller was found for + * handling a request. + * + * This event allows you to change the controller that will handle the + * request. + * + * @Event("Symfony\Component\HttpKernel\Event\FilterControllerEvent") + * + * @var string + */ + const CONTROLLER = 'kernel.controller'; + + /** + * The CONTROLLER_ARGUMENTS event occurs once controller arguments have been resolved. + * + * This event allows you to change the arguments that will be passed to + * the controller. + * + * @Event("Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent") + * + * @var string + */ + const CONTROLLER_ARGUMENTS = 'kernel.controller_arguments'; + + /** + * The RESPONSE event occurs once a response was created for + * replying to a request. + * + * This event allows you to modify or replace the response that will be + * replied. + * + * @Event("Symfony\Component\HttpKernel\Event\FilterResponseEvent") + * + * @var string + */ + const RESPONSE = 'kernel.response'; + + /** + * The TERMINATE event occurs once a response was sent. + * + * This event allows you to run expensive post-response jobs. + * + * @Event("Symfony\Component\HttpKernel\Event\PostResponseEvent") + * + * @var string + */ + const TERMINATE = 'kernel.terminate'; + + /** + * The FINISH_REQUEST event occurs when a response was generated for a request. + * + * This event allows you to reset the global and environmental state of + * the application, when it was changed during the request. + * + * @Event("Symfony\Component\HttpKernel\Event\FinishRequestEvent") + * + * @var string + */ + const FINISH_REQUEST = 'kernel.finish_request'; +} diff --git a/vendor/symfony/http-kernel/KernelInterface.php b/vendor/symfony/http-kernel/KernelInterface.php new file mode 100644 index 00000000..b8609b9e --- /dev/null +++ b/vendor/symfony/http-kernel/KernelInterface.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\Config\Loader\LoaderInterface; + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of bundles. + * + * @author Fabien Potencier + */ +interface KernelInterface extends HttpKernelInterface, \Serializable +{ + /** + * Returns an array of bundles to register. + * + * @return BundleInterface[] An array of bundle instances + */ + public function registerBundles(); + + /** + * Loads the container configuration. + * + * @param LoaderInterface $loader A LoaderInterface instance + */ + public function registerContainerConfiguration(LoaderInterface $loader); + + /** + * Boots the current kernel. + */ + public function boot(); + + /** + * Shutdowns the kernel. + * + * This method is mainly useful when doing functional testing. + */ + public function shutdown(); + + /** + * Gets the registered bundle instances. + * + * @return BundleInterface[] An array of registered bundle instances + */ + public function getBundles(); + + /** + * Returns a bundle and optionally its descendants by its name. + * + * @param string $name Bundle name + * @param bool $first Whether to return the first bundle only or together with its descendants + * + * @return BundleInterface|BundleInterface[] A BundleInterface instance or an array of BundleInterface instances if $first is false + * + * @throws \InvalidArgumentException when the bundle is not enabled + */ + public function getBundle($name, $first = true); + + /** + * Returns the file path for a given resource. + * + * A Resource can be a file or a directory. + * + * The resource name must follow the following pattern: + * + * "@BundleName/path/to/a/file.something" + * + * where BundleName is the name of the bundle + * and the remaining part is the relative path in the bundle. + * + * If $dir is passed, and the first segment of the path is "Resources", + * this method will look for a file named: + * + * $dir//path/without/Resources + * + * before looking in the bundle resource folder. + * + * @param string $name A resource name to locate + * @param string $dir A directory where to look for the resource first + * @param bool $first Whether to return the first path or paths for all matching bundles + * + * @return string|array The absolute path of the resource or an array if $first is false + * + * @throws \InvalidArgumentException if the file cannot be found or the name is not valid + * @throws \RuntimeException if the name contains invalid/unsafe characters + */ + public function locateResource($name, $dir = null, $first = true); + + /** + * Gets the name of the kernel. + * + * @return string The kernel name + */ + public function getName(); + + /** + * Gets the environment. + * + * @return string The current environment + */ + public function getEnvironment(); + + /** + * Checks if debug mode is enabled. + * + * @return bool true if debug mode is enabled, false otherwise + */ + public function isDebug(); + + /** + * Gets the application root dir (path of the project's Kernel class). + * + * @return string The Kernel root dir + */ + public function getRootDir(); + + /** + * Gets the current container. + * + * @return ContainerInterface A ContainerInterface instance + */ + public function getContainer(); + + /** + * Gets the request start time (not available if debug is disabled). + * + * @return int The request start timestamp + */ + public function getStartTime(); + + /** + * Gets the cache directory. + * + * @return string The cache directory + */ + public function getCacheDir(); + + /** + * Gets the log directory. + * + * @return string The log directory + */ + public function getLogDir(); + + /** + * Gets the charset of the application. + * + * @return string The charset + */ + public function getCharset(); +} diff --git a/vendor/symfony/http-kernel/LICENSE b/vendor/symfony/http-kernel/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/http-kernel/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php new file mode 100644 index 00000000..5635a218 --- /dev/null +++ b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +/** + * DebugLoggerInterface. + * + * @author Fabien Potencier + */ +interface DebugLoggerInterface +{ + /** + * Returns an array of logs. + * + * A log is an array with the following mandatory keys: + * timestamp, message, priority, and priorityName. + * It can also have an optional context key containing an array. + * + * @return array An array of logs + */ + public function getLogs(); + + /** + * Returns the number of errors. + * + * @return int The number of errors + */ + public function countErrors(); +} diff --git a/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php new file mode 100644 index 00000000..bd8761f5 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php @@ -0,0 +1,284 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * Storage for profiler using files. + * + * @author Alexandre Salomé + */ +class FileProfilerStorage implements ProfilerStorageInterface +{ + /** + * Folder where profiler data are stored. + * + * @var string + */ + private $folder; + + /** + * Constructs the file storage using a "dsn-like" path. + * + * Example : "file:/path/to/the/storage/folder" + * + * @param string $dsn The DSN + * + * @throws \RuntimeException + */ + public function __construct($dsn) + { + if (0 !== strpos($dsn, 'file:')) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn)); + } + $this->folder = substr($dsn, 5); + + if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder)); + } + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null, $statusCode = null) + { + $file = $this->getIndexFilename(); + + if (!file_exists($file)) { + return array(); + } + + $file = fopen($file, 'r'); + fseek($file, 0, SEEK_END); + + $result = array(); + while (count($result) < $limit && $line = $this->readLineFromFile($file)) { + $values = str_getcsv($line); + list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode) = $values; + $csvTime = (int) $csvTime; + + if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method) || $statusCode && false === strpos($csvStatusCode, $statusCode)) { + continue; + } + + if (!empty($start) && $csvTime < $start) { + continue; + } + + if (!empty($end) && $csvTime > $end) { + continue; + } + + $result[$csvToken] = array( + 'token' => $csvToken, + 'ip' => $csvIp, + 'method' => $csvMethod, + 'url' => $csvUrl, + 'time' => $csvTime, + 'parent' => $csvParent, + 'status_code' => $csvStatusCode, + ); + } + + fclose($file); + + return array_values($result); + } + + /** + * {@inheritdoc} + */ + public function purge() + { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($this->folder, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } else { + rmdir($file); + } + } + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + if (!$token || !file_exists($file = $this->getFilename($token))) { + return; + } + + return $this->createProfileFromData($token, unserialize(file_get_contents($file))); + } + + /** + * {@inheritdoc} + * + * @throws \RuntimeException + */ + public function write(Profile $profile) + { + $file = $this->getFilename($profile->getToken()); + + $profileIndexed = is_file($file); + if (!$profileIndexed) { + // Create directory + $dir = dirname($file); + if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir)); + } + } + + // Store profile + $data = array( + 'token' => $profile->getToken(), + 'parent' => $profile->getParentToken(), + 'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()), + 'data' => $profile->getCollectors(), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + 'status_code' => $profile->getStatusCode(), + ); + + if (false === file_put_contents($file, serialize($data))) { + return false; + } + + if (!$profileIndexed) { + // Add to index + if (false === $file = fopen($this->getIndexFilename(), 'a')) { + return false; + } + + fputcsv($file, array( + $profile->getToken(), + $profile->getIp(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime(), + $profile->getParentToken(), + $profile->getStatusCode(), + )); + fclose($file); + } + + return true; + } + + /** + * Gets filename to store data, associated to the token. + * + * @param string $token + * + * @return string The profile filename + */ + protected function getFilename($token) + { + // Uses 4 last characters, because first are mostly the same. + $folderA = substr($token, -2, 2); + $folderB = substr($token, -4, 2); + + return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token; + } + + /** + * Gets the index filename. + * + * @return string The index filename + */ + protected function getIndexFilename() + { + return $this->folder.'/index.csv'; + } + + /** + * Reads a line in the file, backward. + * + * This function automatically skips the empty lines and do not include the line return in result value. + * + * @param resource $file The file resource, with the pointer placed at the end of the line to read + * + * @return mixed A string representing the line or null if beginning of file is reached + */ + protected function readLineFromFile($file) + { + $line = ''; + $position = ftell($file); + + if (0 === $position) { + return; + } + + while (true) { + $chunkSize = min($position, 1024); + $position -= $chunkSize; + fseek($file, $position); + + if (0 === $chunkSize) { + // bof reached + break; + } + + $buffer = fread($file, $chunkSize); + + if (false === ($upTo = strrpos($buffer, "\n"))) { + $line = $buffer.$line; + continue; + } + + $position += $upTo; + $line = substr($buffer, $upTo + 1).$line; + fseek($file, max(0, $position), SEEK_SET); + + if ('' !== $line) { + break; + } + } + + return '' === $line ? null : $line; + } + + protected function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setStatusCode($data['status_code']); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (!$token || !file_exists($file = $this->getFilename($token))) { + continue; + } + + $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile)); + } + + return $profile; + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profile.php b/vendor/symfony/http-kernel/Profiler/Profile.php new file mode 100644 index 00000000..fab8b41d --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profile.php @@ -0,0 +1,295 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; + +/** + * Profile. + * + * @author Fabien Potencier + */ +class Profile +{ + private $token; + + /** + * @var DataCollectorInterface[] + */ + private $collectors = array(); + + private $ip; + private $method; + private $url; + private $time; + private $statusCode; + + /** + * @var Profile + */ + private $parent; + + /** + * @var Profile[] + */ + private $children = array(); + + /** + * Constructor. + * + * @param string $token The token + */ + public function __construct($token) + { + $this->token = $token; + } + + /** + * Sets the token. + * + * @param string $token The token + */ + public function setToken($token) + { + $this->token = $token; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() + { + return $this->token; + } + + /** + * Sets the parent token. + * + * @param Profile $parent + */ + public function setParent(Profile $parent) + { + $this->parent = $parent; + } + + /** + * Returns the parent profile. + * + * @return self + */ + public function getParent() + { + return $this->parent; + } + + /** + * Returns the parent token. + * + * @return null|string The parent token + */ + public function getParentToken() + { + return $this->parent ? $this->parent->getToken() : null; + } + + /** + * Returns the IP. + * + * @return string The IP + */ + public function getIp() + { + return $this->ip; + } + + /** + * Sets the IP. + * + * @param string $ip + */ + public function setIp($ip) + { + $this->ip = $ip; + } + + /** + * Returns the request method. + * + * @return string The request method + */ + public function getMethod() + { + return $this->method; + } + + public function setMethod($method) + { + $this->method = $method; + } + + /** + * Returns the URL. + * + * @return string The URL + */ + public function getUrl() + { + return $this->url; + } + + public function setUrl($url) + { + $this->url = $url; + } + + /** + * Returns the time. + * + * @return int The time + */ + public function getTime() + { + if (null === $this->time) { + return 0; + } + + return $this->time; + } + + /** + * @param int The time + */ + public function setTime($time) + { + $this->time = $time; + } + + /** + * @param int $statusCode + */ + public function setStatusCode($statusCode) + { + $this->statusCode = $statusCode; + } + + /** + * @return int + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Finds children profilers. + * + * @return self[] + */ + public function getChildren() + { + return $this->children; + } + + /** + * Sets children profiler. + * + * @param Profile[] $children + */ + public function setChildren(array $children) + { + $this->children = array(); + foreach ($children as $child) { + $this->addChild($child); + } + } + + /** + * Adds the child token. + * + * @param Profile $child + */ + public function addChild(Profile $child) + { + $this->children[] = $child; + $child->setParent($this); + } + + /** + * Gets a Collector by name. + * + * @param string $name A collector name + * + * @return DataCollectorInterface A DataCollectorInterface instance + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function getCollector($name) + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } + + /** + * Gets the Collectors associated with this profile. + * + * @return DataCollectorInterface[] + */ + public function getCollectors() + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profile. + * + * @param DataCollectorInterface[] $collectors + */ + public function setCollectors(array $collectors) + { + $this->collectors = array(); + foreach ($collectors as $collector) { + $this->addCollector($collector); + } + } + + /** + * Adds a Collector. + * + * @param DataCollectorInterface $collector A DataCollectorInterface instance + */ + public function addCollector(DataCollectorInterface $collector) + { + $this->collectors[$collector->getName()] = $collector; + } + + /** + * Returns true if a Collector for the given name exists. + * + * @param string $name A collector name + * + * @return bool + */ + public function hasCollector($name) + { + return isset($this->collectors[$name]); + } + + public function __sleep() + { + return array('token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time', 'statusCode'); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profiler.php b/vendor/symfony/http-kernel/Profiler/Profiler.php new file mode 100644 index 00000000..f31e2437 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profiler.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Psr\Log\LoggerInterface; + +/** + * Profiler. + * + * @author Fabien Potencier + */ +class Profiler +{ + /** + * @var ProfilerStorageInterface + */ + private $storage; + + /** + * @var DataCollectorInterface[] + */ + private $collectors = array(); + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var bool + */ + private $enabled = true; + + /** + * Constructor. + * + * @param ProfilerStorageInterface $storage A ProfilerStorageInterface instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null) + { + $this->storage = $storage; + $this->logger = $logger; + } + + /** + * Disables the profiler. + */ + public function disable() + { + $this->enabled = false; + } + + /** + * Enables the profiler. + */ + public function enable() + { + $this->enabled = true; + } + + /** + * Loads the Profile for the given Response. + * + * @param Response $response A Response instance + * + * @return Profile|false A Profile instance + */ + public function loadProfileFromResponse(Response $response) + { + if (!$token = $response->headers->get('X-Debug-Token')) { + return false; + } + + return $this->loadProfile($token); + } + + /** + * Loads the Profile for the given token. + * + * @param string $token A token + * + * @return Profile A Profile instance + */ + public function loadProfile($token) + { + return $this->storage->read($token); + } + + /** + * Saves a Profile. + * + * @param Profile $profile A Profile instance + * + * @return bool + */ + public function saveProfile(Profile $profile) + { + // late collect + foreach ($profile->getCollectors() as $collector) { + if ($collector instanceof LateDataCollectorInterface) { + $collector->lateCollect(); + } + } + + if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { + $this->logger->warning('Unable to store the profiler information.', array('configured_storage' => get_class($this->storage))); + } + + return $ret; + } + + /** + * Purges all data from the storage. + */ + public function purge() + { + $this->storage->purge(); + } + + /** + * Finds profiler tokens for the given criteria. + * + * @param string $ip The IP + * @param string $url The URL + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * @param string $start The start date to search from + * @param string $end The end date to search to + * @param string $statusCode The request status code + * + * @return array An array of tokens + * + * @see http://php.net/manual/en/datetime.formats.php for the supported date/time formats + */ + public function find($ip, $url, $limit, $method, $start, $end, $statusCode = null) + { + return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode); + } + + /** + * Collects data for the given Response. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * @param \Exception $exception An exception instance if the request threw one + * + * @return Profile|null A Profile instance or null if the profiler is disabled + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (false === $this->enabled) { + return; + } + + $profile = new Profile(substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); + $profile->setTime(time()); + $profile->setUrl($request->getUri()); + $profile->setMethod($request->getMethod()); + $profile->setStatusCode($response->getStatusCode()); + try { + $profile->setIp($request->getClientIp()); + } catch (ConflictingHeadersException $e) { + $profile->setIp('Unknown'); + } + + $response->headers->set('X-Debug-Token', $profile->getToken()); + + foreach ($this->collectors as $collector) { + $collector->collect($request, $response, $exception); + + // we need to clone for sub-requests + $profile->addCollector(clone $collector); + } + + return $profile; + } + + /** + * Gets the Collectors associated with this profiler. + * + * @return array An array of collectors + */ + public function all() + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profiler. + * + * @param DataCollectorInterface[] $collectors An array of collectors + */ + public function set(array $collectors = array()) + { + $this->collectors = array(); + foreach ($collectors as $collector) { + $this->add($collector); + } + } + + /** + * Adds a Collector. + * + * @param DataCollectorInterface $collector A DataCollectorInterface instance + */ + public function add(DataCollectorInterface $collector) + { + $this->collectors[$collector->getName()] = $collector; + } + + /** + * Returns true if a Collector for the given name exists. + * + * @param string $name A collector name + * + * @return bool + */ + public function has($name) + { + return isset($this->collectors[$name]); + } + + /** + * Gets a Collector by name. + * + * @param string $name A collector name + * + * @return DataCollectorInterface A DataCollectorInterface instance + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function get($name) + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } + + private function getTimestamp($value) + { + if (null === $value || '' == $value) { + return; + } + + try { + $value = new \DateTime(is_numeric($value) ? '@'.$value : $value); + } catch (\Exception $e) { + return; + } + + return $value->getTimestamp(); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php new file mode 100644 index 00000000..ea72af23 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * ProfilerStorageInterface. + * + * @author Fabien Potencier + */ +interface ProfilerStorageInterface +{ + /** + * Finds profiler tokens for the given criteria. + * + * @param string $ip The IP + * @param string $url The URL + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * @param int|null $start The start date to search from + * @param int|null $end The end date to search to + * + * @return array An array of tokens + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null); + + /** + * Reads data associated with the given token. + * + * The method returns false if the token does not exist in the storage. + * + * @param string $token A token + * + * @return Profile The profile associated with token + */ + public function read($token); + + /** + * Saves a Profile. + * + * @param Profile $profile A Profile instance + * + * @return bool Write operation successful + */ + public function write(Profile $profile); + + /** + * Purges all data from the database. + */ + public function purge(); +} diff --git a/vendor/symfony/http-kernel/README.md b/vendor/symfony/http-kernel/README.md new file mode 100644 index 00000000..cc5e74b6 --- /dev/null +++ b/vendor/symfony/http-kernel/README.md @@ -0,0 +1,16 @@ +HttpKernel Component +==================== + +The HttpKernel component provides a structured process for converting a Request +into a Response by making use of the EventDispatcher component. It's flexible +enough to create a full-stack framework (Symfony), a micro-framework (Silex) or +an advanced CMS system (Drupal). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/http_kernel/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/http-kernel/TerminableInterface.php b/vendor/symfony/http-kernel/TerminableInterface.php new file mode 100644 index 00000000..d55a15b8 --- /dev/null +++ b/vendor/symfony/http-kernel/TerminableInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Terminable extends the Kernel request/response cycle with dispatching a post + * response event after sending the response and before shutting down the kernel. + * + * @author Jordi Boggiano + * @author Pierre Minnieur + */ +interface TerminableInterface +{ + /** + * Terminates a request/response cycle. + * + * Should be called after sending the response and before shutting down the kernel. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + */ + public function terminate(Request $request, Response $response); +} diff --git a/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php b/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php new file mode 100644 index 00000000..eeefccab --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Bundle; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionNotValidBundle\ExtensionNotValidBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\ExtensionPresentBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionAbsentBundle\ExtensionAbsentBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command\FooCommand; + +class BundleTest extends TestCase +{ + public function testGetContainerExtension() + { + $bundle = new ExtensionPresentBundle(); + + $this->assertInstanceOf( + 'Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\DependencyInjection\ExtensionPresentExtension', + $bundle->getContainerExtension() + ); + } + + public function testRegisterCommands() + { + $cmd = new FooCommand(); + $app = $this->getMockBuilder('Symfony\Component\Console\Application')->getMock(); + $app->expects($this->once())->method('add')->with($this->equalTo($cmd)); + + $bundle = new ExtensionPresentBundle(); + $bundle->registerCommands($app); + + $bundle2 = new ExtensionAbsentBundle(); + + $this->assertNull($bundle2->registerCommands($app)); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface + */ + public function testGetContainerExtensionWithInvalidClass() + { + $bundle = new ExtensionNotValidBundle(); + $bundle->getContainerExtension(); + } + + public function testHttpKernelRegisterCommandsIgnoresCommandsThatAreRegisteredAsServices() + { + $container = new ContainerBuilder(); + $container->register('console.command.symfony_component_httpkernel_tests_fixtures_extensionpresentbundle_command_foocommand', 'Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command\FooCommand'); + + $application = $this->getMockBuilder('Symfony\Component\Console\Application')->getMock(); + // add() is never called when the found command classes are already registered as services + $application->expects($this->never())->method('add'); + + $bundle = new ExtensionPresentBundle(); + $bundle->setContainer($container); + $bundle->registerCommands($application); + } + + public function testBundleNameIsGuessedFromClass() + { + $bundle = new GuessedNameBundle(); + + $this->assertSame('Symfony\Component\HttpKernel\Tests\Bundle', $bundle->getNamespace()); + $this->assertSame('GuessedNameBundle', $bundle->getName()); + } + + public function testBundleNameCanBeExplicitlyProvided() + { + $bundle = new NamedBundle(); + + $this->assertSame('ExplicitlyNamedBundle', $bundle->getName()); + $this->assertSame('Symfony\Component\HttpKernel\Tests\Bundle', $bundle->getNamespace()); + $this->assertSame('ExplicitlyNamedBundle', $bundle->getName()); + } +} + +class NamedBundle extends Bundle +{ + public function __construct() + { + $this->name = 'ExplicitlyNamedBundle'; + } +} + +class GuessedNameBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php b/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php new file mode 100644 index 00000000..1bc85334 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheClearer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; + +class ChainCacheClearerTest extends TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_clearer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheDir); + } + + public function testInjectClearersInConstructor() + { + $clearer = $this->getMockClearer(); + $clearer + ->expects($this->once()) + ->method('clear'); + + $chainClearer = new ChainCacheClearer(array($clearer)); + $chainClearer->clear(self::$cacheDir); + } + + public function testInjectClearerUsingAdd() + { + $clearer = $this->getMockClearer(); + $clearer + ->expects($this->once()) + ->method('clear'); + + $chainClearer = new ChainCacheClearer(); + $chainClearer->add($clearer); + $chainClearer->clear(self::$cacheDir); + } + + protected function getMockClearer() + { + return $this->getMockBuilder('Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface')->getMock(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php b/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php new file mode 100644 index 00000000..a5d9b6ef --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheClearer/Psr6CacheClearerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheClearer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; +use Psr\Cache\CacheItemPoolInterface; + +class Psr6CacheClearerTest extends TestCase +{ + public function testClearPoolsInjectedInConstructor() + { + $pool = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool + ->expects($this->once()) + ->method('clear'); + + (new Psr6CacheClearer(array('pool' => $pool)))->clear(''); + } + + public function testClearPool() + { + $pool = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool + ->expects($this->once()) + ->method('clear'); + + (new Psr6CacheClearer(array('pool' => $pool)))->clearPool('pool'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cache pool not found: unknown + */ + public function testClearPoolThrowsExceptionOnUnreferencedPool() + { + (new Psr6CacheClearer())->clearPool('unknown'); + } + + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer::addPool() method is deprecated since version 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead. + */ + public function testClearPoolsInjectedByAdder() + { + $pool1 = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool1 + ->expects($this->once()) + ->method('clear'); + + $pool2 = $this->getMockBuilder(CacheItemPoolInterface::class)->getMock(); + $pool2 + ->expects($this->once()) + ->method('clear'); + + $clearer = new Psr6CacheClearer(array('pool1' => $pool1)); + $clearer->addPool($pool2); + $clearer->clear(''); + } +} diff --git a/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php new file mode 100644 index 00000000..d07ade30 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheWarmer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; + +class CacheWarmerAggregateTest extends TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheDir); + } + + public function testInjectWarmersUsingConstructor() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingAdd() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->add($warmer); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingSetWarmers() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->setWarmers(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->never()) + ->method('isOptional'); + $warmer + ->expects($this->once()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->enableOptionalWarmers(); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesNotCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsNotEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('isOptional') + ->will($this->returnValue(true)); + $warmer + ->expects($this->never()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + protected function getCacheWarmerMock() + { + $warmer = $this->getMockBuilder('Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface') + ->disableOriginalConstructor() + ->getMock(); + + return $warmer; + } +} diff --git a/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php new file mode 100644 index 00000000..05666cb0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheWarmer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerTest extends TestCase +{ + protected static $cacheFile; + + public static function setUpBeforeClass() + { + self::$cacheFile = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheFile); + } + + public function testWriteCacheFileCreatesTheFile() + { + $warmer = new TestCacheWarmer(self::$cacheFile); + $warmer->warmUp(dirname(self::$cacheFile)); + + $this->assertFileExists(self::$cacheFile); + } + + /** + * @expectedException \RuntimeException + */ + public function testWriteNonWritableCacheFileThrowsARuntimeException() + { + $nonWritableFile = '/this/file/is/very/probably/not/writable'; + $warmer = new TestCacheWarmer($nonWritableFile); + $warmer->warmUp(dirname($nonWritableFile)); + } +} + +class TestCacheWarmer extends CacheWarmer +{ + protected $file; + + public function __construct($file) + { + $this->file = $file; + } + + public function warmUp($cacheDir) + { + $this->writeCacheFile($this->file, 'content'); + } + + public function isOptional() + { + return false; + } +} diff --git a/vendor/symfony/http-kernel/Tests/ClientTest.php b/vendor/symfony/http-kernel/Tests/ClientTest.php new file mode 100644 index 00000000..1ac72c73 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ClientTest.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpKernel\Tests\Fixtures\TestClient; + +/** + * @group time-sensitive + */ +class ClientTest extends TestCase +{ + public function testDoRequest() + { + $client = new Client(new TestHttpKernel()); + + $client->request('GET', '/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Request', $client->getInternalRequest()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Request', $client->getRequest()); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getInternalResponse()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $client->getResponse()); + + $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + $this->assertEquals('www.example.com', $client->getRequest()->getHost(), '->doRequest() uses the request handler to make the request'); + + $client->request('GET', 'http://www.example.com/?parameter=http://google.com'); + $this->assertEquals('http://www.example.com/?parameter='.urlencode('http://google.com'), $client->getRequest()->getUri(), '->doRequest() uses the request handler to make the request'); + } + + public function testGetScript() + { + $client = new TestClient(new TestHttpKernel()); + $client->insulate(); + $client->request('GET', '/'); + + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->getScript() returns a script that uses the request handler to make the request'); + } + + public function testFilterResponseConvertsCookies() + { + $client = new Client(new TestHttpKernel()); + + $r = new \ReflectionObject($client); + $m = $r->getMethod('filterResponse'); + $m->setAccessible(true); + + $expected = array( + 'foo=bar; expires=Sun, 15-Feb-2009 20:00:00 GMT; max-age='.(strtotime('Sun, 15-Feb-2009 20:00:00 GMT') - time()).'; path=/foo; domain=http://example.com; secure; httponly', + 'foo1=bar1; expires=Sun, 15-Feb-2009 20:00:00 GMT; max-age='.(strtotime('Sun, 15-Feb-2009 20:00:00 GMT') - time()).'; path=/foo; domain=http://example.com; secure; httponly', + ); + + $response = new Response(); + $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $domResponse = $m->invoke($client, $response); + $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); + + $response = new Response(); + $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $response->headers->setCookie(new Cookie('foo1', 'bar1', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $domResponse = $m->invoke($client, $response); + $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); + $this->assertEquals($expected, $domResponse->getHeader('Set-Cookie', false)); + } + + public function testFilterResponseSupportsStreamedResponses() + { + $client = new Client(new TestHttpKernel()); + + $r = new \ReflectionObject($client); + $m = $r->getMethod('filterResponse'); + $m->setAccessible(true); + + $response = new StreamedResponse(function () { + echo 'foo'; + }); + + $domResponse = $m->invoke($client, $response); + $this->assertEquals('foo', $domResponse->getContent()); + } + + public function testUploadedFile() + { + $source = tempnam(sys_get_temp_dir(), 'source'); + $target = sys_get_temp_dir().'/sf.moved.file'; + @unlink($target); + + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $files = array( + array('tmp_name' => $source, 'name' => 'original', 'type' => 'mime/original', 'size' => 123, 'error' => UPLOAD_ERR_OK), + new UploadedFile($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK, true), + ); + + $file = null; + foreach ($files as $file) { + $client->request('POST', '/', array(), array('foo' => $file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + + $file = $files['foo']; + + $this->assertEquals('original', $file->getClientOriginalName()); + $this->assertEquals('mime/original', $file->getClientMimeType()); + $this->assertEquals('123', $file->getClientSize()); + $this->assertTrue($file->isValid()); + } + + $file->move(dirname($target), basename($target)); + + $this->assertFileExists($target); + unlink($target); + } + + public function testUploadedFileWhenNoFileSelected() + { + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $file = array('tmp_name' => '', 'name' => '', 'type' => '', 'size' => 0, 'error' => UPLOAD_ERR_NO_FILE); + + $client->request('POST', '/', array(), array('foo' => $file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + $this->assertNull($files['foo']); + } + + public function testUploadedFileWhenSizeExceedsUploadMaxFileSize() + { + $source = tempnam(sys_get_temp_dir(), 'source'); + + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $file = $this + ->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') + ->setConstructorArgs(array($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK, true)) + ->setMethods(array('getSize')) + ->getMock() + ; + + $file->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(INF)) + ; + + $client->request('POST', '/', array(), array($file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + + $file = $files[0]; + + $this->assertFalse($file->isValid()); + $this->assertEquals(UPLOAD_ERR_INI_SIZE, $file->getError()); + $this->assertEquals('mime/original', $file->getClientMimeType()); + $this->assertEquals('original', $file->getClientOriginalName()); + $this->assertEquals(0, $file->getClientSize()); + + unlink($source); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php b/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php new file mode 100644 index 00000000..6fe8a675 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Config; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; + +class EnvParametersResourceTest extends TestCase +{ + protected $prefix = '__DUMMY_'; + protected $initialEnv; + protected $resource; + + protected function setUp() + { + $this->initialEnv = array( + $this->prefix.'1' => 'foo', + $this->prefix.'2' => 'bar', + ); + + foreach ($this->initialEnv as $key => $value) { + $_SERVER[$key] = $value; + } + + $this->resource = new EnvParametersResource($this->prefix); + } + + protected function tearDown() + { + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, $this->prefix)) { + unset($_SERVER[$key]); + } + } + } + + public function testGetResource() + { + $this->assertSame( + array('prefix' => $this->prefix, 'variables' => $this->initialEnv), + $this->resource->getResource(), + '->getResource() returns the resource' + ); + } + + public function testToString() + { + $this->assertSame( + serialize(array('prefix' => $this->prefix, 'variables' => $this->initialEnv)), + (string) $this->resource + ); + } + + public function testIsFreshNotChanged() + { + $this->assertTrue( + $this->resource->isFresh(time()), + '->isFresh() returns true if the variables have not changed' + ); + } + + public function testIsFreshValueChanged() + { + reset($this->initialEnv); + $_SERVER[key($this->initialEnv)] = 'baz'; + + $this->assertFalse( + $this->resource->isFresh(time()), + '->isFresh() returns false if a variable has been changed' + ); + } + + public function testIsFreshValueRemoved() + { + reset($this->initialEnv); + unset($_SERVER[key($this->initialEnv)]); + + $this->assertFalse( + $this->resource->isFresh(time()), + '->isFresh() returns false if a variable has been removed' + ); + } + + public function testIsFreshValueAdded() + { + $_SERVER[$this->prefix.'3'] = 'foo'; + + $this->assertFalse( + $this->resource->isFresh(time()), + '->isFresh() returns false if a variable has been added' + ); + } + + public function testSerializeUnserialize() + { + $this->assertEquals($this->resource, unserialize(serialize($this->resource))); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php b/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php new file mode 100644 index 00000000..6265f027 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Config; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Config\FileLocator; + +class FileLocatorTest extends TestCase +{ + public function testLocate() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel + ->expects($this->atLeastOnce()) + ->method('locateResource') + ->with('@BundleName/some/path', null, true) + ->will($this->returnValue('/bundle-name/some/path')); + $locator = new FileLocator($kernel); + $this->assertEquals('/bundle-name/some/path', $locator->locate('@BundleName/some/path')); + + $kernel + ->expects($this->never()) + ->method('locateResource'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('LogicException'); + $locator->locate('/some/path'); + } + + public function testLocateWithGlobalResourcePath() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel + ->expects($this->atLeastOnce()) + ->method('locateResource') + ->with('@BundleName/some/path', '/global/resource/path', false); + + $locator = new FileLocator($kernel, '/global/resource/path'); + $locator->locate('@BundleName/some/path', null, false); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php new file mode 100644 index 00000000..08041390 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ArgumentResolverTest.php @@ -0,0 +1,349 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Controller; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\ExtendingRequest; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\ExtendingSession; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; +use Symfony\Component\HttpFoundation\Request; + +class ArgumentResolverTest extends TestCase +{ + /** @var ArgumentResolver */ + private static $resolver; + + public static function setUpBeforeClass() + { + $factory = new ArgumentMetadataFactory(); + + self::$resolver = new ArgumentResolver($factory); + } + + public function testDefaultState() + { + $this->assertEquals(self::$resolver, new ArgumentResolver()); + $this->assertNotEquals(self::$resolver, new ArgumentResolver(null, array(new RequestAttributeValueResolver()))); + } + + public function testGetArguments() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerWithFoo'); + + $this->assertEquals(array('foo'), self::$resolver->getArguments($request, $controller), '->getArguments() returns an array of arguments for the controller method'); + } + + public function testGetArgumentsReturnsEmptyArrayWhenNoArguments() + { + $request = Request::create('/'); + $controller = array(new self(), 'controllerWithoutArguments'); + + $this->assertEquals(array(), self::$resolver->getArguments($request, $controller), '->getArguments() returns an empty array if the method takes no arguments'); + } + + public function testGetArgumentsUsesDefaultValue() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerWithFooAndDefaultBar'); + + $this->assertEquals(array('foo', null), self::$resolver->getArguments($request, $controller), '->getArguments() uses default values if present'); + } + + public function testGetArgumentsOverrideDefaultValueByRequestAttribute() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', 'bar'); + $controller = array(new self(), 'controllerWithFooAndDefaultBar'); + + $this->assertEquals(array('foo', 'bar'), self::$resolver->getArguments($request, $controller), '->getArguments() overrides default values if provided in the request attributes'); + } + + public function testGetArgumentsFromClosure() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo) {}; + + $this->assertEquals(array('foo'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsUsesDefaultValueFromClosure() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo, $bar = 'bar') {}; + + $this->assertEquals(array('foo', 'bar'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsFromInvokableObject() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = new self(); + + $this->assertEquals(array('foo', null), self::$resolver->getArguments($request, $controller)); + + // Test default bar overridden by request attribute + $request->attributes->set('bar', 'bar'); + + $this->assertEquals(array('foo', 'bar'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsFromFunctionName() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = __NAMESPACE__.'\controller_function'; + + $this->assertEquals(array('foo', 'foobar'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetArgumentsFailsOnUnresolvedValue() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = array(new self(), 'controllerWithFooBarFoobar'); + + try { + self::$resolver->getArguments($request, $controller); + $this->fail('->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } catch (\Exception $e) { + $this->assertInstanceOf('\RuntimeException', $e, '->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } + } + + public function testGetArgumentsInjectsRequest() + { + $request = Request::create('/'); + $controller = array(new self(), 'controllerWithRequest'); + + $this->assertEquals(array($request), self::$resolver->getArguments($request, $controller), '->getArguments() injects the request'); + } + + public function testGetArgumentsInjectsExtendingRequest() + { + $request = ExtendingRequest::create('/'); + $controller = array(new self(), 'controllerWithExtendingRequest'); + + $this->assertEquals(array($request), self::$resolver->getArguments($request, $controller), '->getArguments() injects the request when extended'); + } + + /** + * @requires PHP 5.6 + */ + public function testGetVariadicArguments() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', array('foo', 'bar')); + $controller = array(new VariadicController(), 'action'); + + $this->assertEquals(array('foo', 'foo', 'bar'), self::$resolver->getArguments($request, $controller)); + } + + /** + * @requires PHP 5.6 + * @expectedException \InvalidArgumentException + */ + public function testGetVariadicArgumentsWithoutArrayInRequest() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', 'foo'); + $controller = array(new VariadicController(), 'action'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @requires PHP 5.6 + * @expectedException \InvalidArgumentException + */ + public function testGetArgumentWithoutArray() + { + $factory = new ArgumentMetadataFactory(); + $valueResolver = $this->getMockBuilder(ArgumentValueResolverInterface::class)->getMock(); + $resolver = new ArgumentResolver($factory, array($valueResolver)); + + $valueResolver->expects($this->any())->method('supports')->willReturn(true); + $valueResolver->expects($this->any())->method('resolve')->willReturn('foo'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', 'foo'); + $controller = array($this, 'controllerWithFooAndDefaultBar'); + $resolver->getArguments($request, $controller); + } + + /** + * @expectedException \RuntimeException + */ + public function testIfExceptionIsThrownWhenMissingAnArgument() + { + $request = Request::create('/'); + $controller = array($this, 'controllerWithFoo'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @requires PHP 7.1 + */ + public function testGetNullableArguments() + { + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', new \stdClass()); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + + $this->assertEquals(array('foo', new \stdClass(), 'value', 'mandatory'), self::$resolver->getArguments($request, $controller)); + } + + /** + * @requires PHP 7.1 + */ + public function testGetNullableArgumentsWithDefaults() + { + $request = Request::create('/'); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + + $this->assertEquals(array(null, null, 'value', 'mandatory'), self::$resolver->getArguments($request, $controller)); + } + + public function testGetSessionArguments() + { + $session = new Session(new MockArraySessionStorage()); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithSession'); + + $this->assertEquals(array($session), self::$resolver->getArguments($request, $controller)); + } + + public function testGetSessionArgumentsWithExtendedSession() + { + $session = new ExtendingSession(new MockArraySessionStorage()); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithExtendingSession'); + + $this->assertEquals(array($session), self::$resolver->getArguments($request, $controller)); + } + + public function testGetSessionArgumentsWithInterface() + { + $session = $this->getMockBuilder(SessionInterface::class)->getMock(); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithSessionInterface'); + + $this->assertEquals(array($session), self::$resolver->getArguments($request, $controller)); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetSessionMissMatchWithInterface() + { + $session = $this->getMockBuilder(SessionInterface::class)->getMock(); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithExtendingSession'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetSessionMissMatchWithImplementation() + { + $session = new Session(new MockArraySessionStorage()); + $request = Request::create('/'); + $request->setSession($session); + $controller = array($this, 'controllerWithExtendingSession'); + + self::$resolver->getArguments($request, $controller); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetSessionMissMatchOnNull() + { + $request = Request::create('/'); + $controller = array($this, 'controllerWithExtendingSession'); + + self::$resolver->getArguments($request, $controller); + } + + public function __invoke($foo, $bar = null) + { + } + + public function controllerWithFoo($foo) + { + } + + public function controllerWithoutArguments() + { + } + + protected function controllerWithFooAndDefaultBar($foo, $bar = null) + { + } + + protected function controllerWithFooBarFoobar($foo, $bar, $foobar) + { + } + + protected function controllerWithRequest(Request $request) + { + } + + protected function controllerWithExtendingRequest(ExtendingRequest $request) + { + } + + protected function controllerWithSession(Session $session) + { + } + + protected function controllerWithSessionInterface(SessionInterface $session) + { + } + + protected function controllerWithExtendingSession(ExtendingSession $session) + { + } +} + +function controller_function($foo, $foobar) +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php new file mode 100644 index 00000000..30b535e8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ContainerControllerResolverTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Controller; + +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver; + +class ContainerControllerResolverTest extends ControllerResolverTest +{ + public function testGetControllerService() + { + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($this)) + ; + + $resolver = $this->createControllerResolver(null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', 'foo:controllerMethod1'); + + $controller = $resolver->getController($request); + + $this->assertInstanceOf(get_class($this), $controller[0]); + $this->assertSame('controllerMethod1', $controller[1]); + } + + public function testGetControllerInvokableService() + { + $invokableController = new InvokableController('bar'); + + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('has') + ->with('foo') + ->will($this->returnValue(true)) + ; + $container->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($invokableController)) + ; + + $resolver = $this->createControllerResolver(null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', 'foo'); + + $controller = $resolver->getController($request); + + $this->assertEquals($invokableController, $controller); + } + + public function testGetControllerInvokableServiceWithClassNameAsName() + { + $invokableController = new InvokableController('bar'); + $className = __NAMESPACE__.'\InvokableController'; + + $container = $this->createMockContainer(); + $container->expects($this->once()) + ->method('has') + ->with($className) + ->will($this->returnValue(true)) + ; + $container->expects($this->once()) + ->method('get') + ->with($className) + ->will($this->returnValue($invokableController)) + ; + + $resolver = $this->createControllerResolver(null, $container); + $request = Request::create('/'); + $request->attributes->set('_controller', $className); + + $controller = $resolver->getController($request); + + $this->assertEquals($invokableController, $controller); + } + + /** + * @dataProvider getUndefinedControllers + */ + public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null) + { + // All this logic needs to be duplicated, since calling parent::testGetControllerOnNonUndefinedFunction will override the expected excetion and not use the regex + $resolver = $this->createControllerResolver(); + if (method_exists($this, 'expectException')) { + $this->expectException($exceptionName); + $this->expectExceptionMessageRegExp($exceptionMessage); + } else { + $this->setExpectedExceptionRegExp($exceptionName, $exceptionMessage); + } + + $request = Request::create('/'); + $request->attributes->set('_controller', $controller); + $resolver->getController($request); + } + + public function getUndefinedControllers() + { + return array( + array('foo', \LogicException::class, '/Unable to parse the controller name "foo"\./'), + array('oof::bar', \InvalidArgumentException::class, '/Class "oof" does not exist\./'), + array('stdClass', \LogicException::class, '/Unable to parse the controller name "stdClass"\./'), + array( + 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar', + \InvalidArgumentException::class, + '/.?[cC]ontroller(.*?) for URI "\/" is not callable\.( Expected method(.*) Available methods)?/', + ), + ); + } + + protected function createControllerResolver(LoggerInterface $logger = null, ContainerInterface $container = null) + { + if (!$container) { + $container = $this->createMockContainer(); + } + + return new ContainerControllerResolver($container, $logger); + } + + protected function createMockContainer() + { + return $this->getMockBuilder(ContainerInterface::class)->getMock(); + } +} + +class InvokableController +{ + public function __construct($bar) // mandatory argument to prevent automatic instantiation + { + } + + public function __invoke() + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php new file mode 100644 index 00000000..190e15ad --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Controller; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; +use Symfony\Component\HttpFoundation\Request; + +class ControllerResolverTest extends TestCase +{ + public function testGetControllerWithoutControllerParameter() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger->expects($this->once())->method('warning')->with('Unable to look for the controller as the "_controller" parameter is missing.'); + $resolver = $this->createControllerResolver($logger); + + $request = Request::create('/'); + $this->assertFalse($resolver->getController($request), '->getController() returns false when the request has no _controller attribute'); + } + + public function testGetControllerWithLambda() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', $lambda = function () {}); + $controller = $resolver->getController($request); + $this->assertSame($lambda, $controller); + } + + public function testGetControllerWithObjectAndInvokeMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', $this); + $controller = $resolver->getController($request); + $this->assertSame($this, $controller); + } + + public function testGetControllerWithObjectAndMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', array($this, 'controllerMethod1')); + $controller = $resolver->getController($request); + $this->assertSame(array($this, 'controllerMethod1'), $controller); + } + + public function testGetControllerWithClassAndMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', array('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', 'controllerMethod4')); + $controller = $resolver->getController($request); + $this->assertSame(array('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', 'controllerMethod4'), $controller); + } + + public function testGetControllerWithObjectAndMethodAsString() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::controllerMethod1'); + $controller = $resolver->getController($request); + $this->assertInstanceOf('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', $controller[0], '->getController() returns a PHP callable'); + } + + public function testGetControllerWithClassAndInvokeMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest'); + $controller = $resolver->getController($request); + $this->assertInstanceOf('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest', $controller); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetControllerOnObjectWithoutInvokeMethod() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', new \stdClass()); + $resolver->getController($request); + } + + public function testGetControllerWithFunction() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\Controller\some_controller_function'); + $controller = $resolver->getController($request); + $this->assertSame('Symfony\Component\HttpKernel\Tests\Controller\some_controller_function', $controller); + } + + /** + * @dataProvider getUndefinedControllers + */ + public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null) + { + $resolver = $this->createControllerResolver(); + if (method_exists($this, 'expectException')) { + $this->expectException($exceptionName); + $this->expectExceptionMessage($exceptionMessage); + } else { + $this->setExpectedException($exceptionName, $exceptionMessage); + } + + $request = Request::create('/'); + $request->attributes->set('_controller', $controller); + $resolver->getController($request); + } + + public function getUndefinedControllers() + { + return array( + array(1, 'InvalidArgumentException', 'Unable to find controller "1".'), + array('foo', 'InvalidArgumentException', 'Unable to find controller "foo".'), + array('oof::bar', 'InvalidArgumentException', 'Class "oof" does not exist.'), + array('stdClass', 'InvalidArgumentException', 'Unable to find controller "stdClass".'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::staticsAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Expected method "staticsAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest", did you mean "staticAction"?'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::privateAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Method "privateAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::protectedAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Method "protectedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'), + array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::undefinedAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Expected method "undefinedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"'), + ); + } + + /** + * @group legacy + */ + public function testGetArguments() + { + $resolver = $this->createControllerResolver(); + + $request = Request::create('/'); + $controller = array(new self(), 'testGetArguments'); + $this->assertEquals(array(), $resolver->getArguments($request, $controller), '->getArguments() returns an empty array if the method takes no arguments'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod1'); + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller), '->getArguments() returns an array of arguments for the controller method'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod2'); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller), '->getArguments() uses default values if present'); + + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller), '->getArguments() overrides default values if provided in the request attributes'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo) {}; + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo, $bar = 'bar') {}; + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = new self(); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller)); + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = 'Symfony\Component\HttpKernel\Tests\Controller\some_controller_function'; + $this->assertEquals(array('foo', 'foobar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = array(new self(), 'controllerMethod3'); + + try { + $resolver->getArguments($request, $controller); + $this->fail('->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } catch (\Exception $e) { + $this->assertInstanceOf('\RuntimeException', $e, '->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } + + $request = Request::create('/'); + $controller = array(new self(), 'controllerMethod5'); + $this->assertEquals(array($request), $resolver->getArguments($request, $controller), '->getArguments() injects the request'); + } + + /** + * @requires PHP 5.6 + * @group legacy + */ + public function testGetVariadicArguments() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', array('foo', 'bar')); + $controller = array(new VariadicController(), 'action'); + $this->assertEquals(array('foo', 'foo', 'bar'), $resolver->getArguments($request, $controller)); + } + + public function testCreateControllerCanReturnAnyCallable() + { + $mock = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ControllerResolver')->setMethods(array('createController'))->getMock(); + $mock->expects($this->once())->method('createController')->will($this->returnValue('Symfony\Component\HttpKernel\Tests\Controller\some_controller_function')); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'foobar'); + $mock->getController($request); + } + + /** + * @expectedException \RuntimeException + * @group legacy + */ + public function testIfExceptionIsThrownWhenMissingAnArgument() + { + $resolver = new ControllerResolver(); + $request = Request::create('/'); + + $controller = array($this, 'controllerMethod1'); + + $resolver->getArguments($request, $controller); + } + + /** + * @requires PHP 7.1 + * @group legacy + */ + public function testGetNullableArguments() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', new \stdClass()); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + $this->assertEquals(array('foo', new \stdClass(), 'value', 'mandatory'), $resolver->getArguments($request, $controller)); + } + + /** + * @requires PHP 7.1 + * @group legacy + */ + public function testGetNullableArgumentsWithDefaults() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('mandatory', 'mandatory'); + $controller = array(new NullableController(), 'action'); + $this->assertEquals(array(null, null, 'value', 'mandatory'), $resolver->getArguments($request, $controller)); + } + + protected function createControllerResolver(LoggerInterface $logger = null) + { + return new ControllerResolver($logger); + } + + public function __invoke($foo, $bar = null) + { + } + + public function controllerMethod1($foo) + { + } + + protected function controllerMethod2($foo, $bar = null) + { + } + + protected function controllerMethod3($foo, $bar, $foobar) + { + } + + protected static function controllerMethod4() + { + } + + protected function controllerMethod5(Request $request) + { + } +} + +function some_controller_function($foo, $foobar) +{ +} + +class ControllerTest +{ + public function publicAction() + { + } + + private function privateAction() + { + } + + protected function protectedAction() + { + } + + public static function staticAction() + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php new file mode 100644 index 00000000..b4b449f3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\ControllerMetadata; + +use Fake\ImportedAndFake; +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\BasicTypesController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\NullableController; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; + +class ArgumentMetadataFactoryTest extends TestCase +{ + /** + * @var ArgumentMetadataFactory + */ + private $factory; + + protected function setUp() + { + $this->factory = new ArgumentMetadataFactory(); + } + + public function testSignature1() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature1')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', self::class, false, false, null), + new ArgumentMetadata('bar', 'array', false, false, null), + new ArgumentMetadata('baz', 'callable', false, false, null), + ), $arguments); + } + + public function testSignature2() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature2')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', self::class, false, true, null, true), + new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, true, null, true), + new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, true, null, true), + ), $arguments); + } + + public function testSignature3() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature3')); + + $this->assertEquals(array( + new ArgumentMetadata('bar', __NAMESPACE__.'\FakeClassThatDoesNotExist', false, false, null), + new ArgumentMetadata('baz', 'Fake\ImportedAndFake', false, false, null), + ), $arguments); + } + + public function testSignature4() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature4')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', null, false, true, 'default'), + new ArgumentMetadata('bar', null, false, true, 500), + new ArgumentMetadata('baz', null, false, true, array()), + ), $arguments); + } + + public function testSignature5() + { + $arguments = $this->factory->createArgumentMetadata(array($this, 'signature5')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', 'array', false, true, null, true), + new ArgumentMetadata('bar', null, false, false, null), + ), $arguments); + } + + /** + * @requires PHP 5.6 + */ + public function testVariadicSignature() + { + $arguments = $this->factory->createArgumentMetadata(array(new VariadicController(), 'action')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', null, false, false, null), + new ArgumentMetadata('bar', null, true, false, null), + ), $arguments); + } + + /** + * @requires PHP 7.0 + */ + public function testBasicTypesSignature() + { + $arguments = $this->factory->createArgumentMetadata(array(new BasicTypesController(), 'action')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', 'string', false, false, null), + new ArgumentMetadata('bar', 'int', false, false, null), + new ArgumentMetadata('baz', 'float', false, false, null), + ), $arguments); + } + + /** + * @requires PHP 7.1 + */ + public function testNullableTypesSignature() + { + $arguments = $this->factory->createArgumentMetadata(array(new NullableController(), 'action')); + + $this->assertEquals(array( + new ArgumentMetadata('foo', 'string', false, false, null, true), + new ArgumentMetadata('bar', \stdClass::class, false, false, null, true), + new ArgumentMetadata('baz', 'string', false, true, 'value', true), + new ArgumentMetadata('mandatory', null, false, false, null, true), + ), $arguments); + } + + private function signature1(ArgumentMetadataFactoryTest $foo, array $bar, callable $baz) + { + } + + private function signature2(ArgumentMetadataFactoryTest $foo = null, FakeClassThatDoesNotExist $bar = null, ImportedAndFake $baz = null) + { + } + + private function signature3(FakeClassThatDoesNotExist $bar, ImportedAndFake $baz) + { + } + + private function signature4($foo = 'default', $bar = 500, $baz = array()) + { + } + + private function signature5(array $foo = null, $bar) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php new file mode 100644 index 00000000..05351445 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ControllerMetadata/ArgumentMetadataTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\ControllerMetadata; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +class ArgumentMetadataTest extends TestCase +{ + public function testWithBcLayerWithDefault() + { + $argument = new ArgumentMetadata('foo', 'string', false, true, 'default value'); + + $this->assertFalse($argument->isNullable()); + } + + public function testDefaultValueAvailable() + { + $argument = new ArgumentMetadata('foo', 'string', false, true, 'default value', true); + + $this->assertTrue($argument->isNullable()); + $this->assertTrue($argument->hasDefaultValue()); + $this->assertSame('default value', $argument->getDefaultValue()); + } + + /** + * @expectedException \LogicException + */ + public function testDefaultValueUnavailable() + { + $argument = new ArgumentMetadata('foo', 'string', false, false, null, false); + + $this->assertFalse($argument->isNullable()); + $this->assertFalse($argument->hasDefaultValue()); + $argument->getDefaultValue(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log b/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log new file mode 100644 index 00000000..88b6840e --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/Compiler.log @@ -0,0 +1,4 @@ +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Psr\Container\ContainerInterface"; reason: private alias. +Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass: Removed service "Symfony\Component\DependencyInjection\ContainerInterface"; reason: private alias. +Some custom logging message +With ending : diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php new file mode 100644 index 00000000..4fb36afd --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ConfigDataCollectorTest extends TestCase +{ + public function testCollect() + { + $kernel = new KernelForTest('test', true); + $c = new ConfigDataCollector(); + $c->setKernel($kernel); + $c->collect(new Request(), new Response()); + + $this->assertSame('test', $c->getEnv()); + $this->assertTrue($c->isDebug()); + $this->assertSame('config', $c->getName()); + $this->assertSame('testkernel', $c->getAppName()); + $this->assertRegExp('~^'.preg_quote($c->getPhpVersion(), '~').'~', PHP_VERSION); + $this->assertRegExp('~'.preg_quote((string) $c->getPhpVersionExtra(), '~').'$~', PHP_VERSION); + $this->assertSame(PHP_INT_SIZE * 8, $c->getPhpArchitecture()); + $this->assertSame(class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', $c->getPhpIntlLocale()); + $this->assertSame(date_default_timezone_get(), $c->getPhpTimezone()); + $this->assertSame(Kernel::VERSION, $c->getSymfonyVersion()); + $this->assertNull($c->getToken()); + $this->assertSame(extension_loaded('xdebug'), $c->hasXDebug()); + $this->assertSame(extension_loaded('Zend OPcache') && ini_get('opcache.enable'), $c->hasZendOpcache()); + $this->assertSame(extension_loaded('apcu') && ini_get('apc.enabled'), $c->hasApcu()); + } +} + +class KernelForTest extends Kernel +{ + public function getName() + { + return 'testkernel'; + } + + public function registerBundles() + { + } + + public function getBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php new file mode 100644 index 00000000..54fd39e0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/DataCollectorTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Fixtures\DataCollector\CloneVarDataCollector; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +class DataCollectorTest extends TestCase +{ + public function testCloneVarStringWithScheme() + { + $c = new CloneVarDataCollector('scheme://foo'); + $c->collect(new Request(), new Response()); + $cloner = new VarCloner(); + + $this->assertEquals($cloner->cloneVar('scheme://foo'), $c->getData()); + } + + public function testCloneVarExistingFilePath() + { + $c = new CloneVarDataCollector(array($filePath = tempnam(sys_get_temp_dir(), 'clone_var_data_collector_'))); + $c->collect(new Request(), new Response()); + + $this->assertSame($filePath, $c->getData()[0]); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php new file mode 100644 index 00000000..9a306e53 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.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\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Nicolas Grekas + */ +class DumpDataCollectorTest extends TestCase +{ + public function testDump() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(); + + $this->assertSame('dump', $collector->getName()); + + $collector->dump($data); + $line = __LINE__ - 1; + $this->assertSame(1, $collector->getDumpsCount()); + + $dump = $collector->getDumps('html'); + $this->assertTrue(isset($dump[0]['data'])); + $dump[0]['data'] = preg_replace('/^.*?
 "
123\n
\n", + 'name' => 'DumpDataCollectorTest.php', + 'file' => __FILE__, + 'line' => $line, + 'fileExcerpt' => false, + ), + ); + $this->assertEquals($xDump, $dump); + + $this->assertStringMatchesFormat('a:3:{i:0;a:5:{s:4:"data";O:39:"Symfony\Component\VarDumper\Cloner\Data":%a', $collector->serialize()); + $this->assertSame(0, $collector->getDumpsCount()); + $this->assertSame('a:2:{i:0;b:0;i:1;s:5:"UTF-8";}', $collector->serialize()); + } + + public function testCollectDefault() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(); + + $collector->dump($data); + $line = __LINE__ - 1; + + ob_start(); + $collector->collect(new Request(), new Response()); + $output = ob_get_clean(); + + $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n123\n", $output); + $this->assertSame(1, $collector->getDumpsCount()); + $collector->serialize(); + } + + public function testCollectHtml() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(null, 'test://%f:%l'); + + $collector->dump($data); + $line = __LINE__ - 1; + $file = __FILE__; + $xOutput = <<DumpDataCollectorTest.php on line {$line}: +123 +
+EOTXT; + + ob_start(); + $response = new Response(); + $response->headers->set('Content-Type', 'text/html'); + $collector->collect(new Request(), $response); + $output = ob_get_clean(); + $output = preg_replace('#<(script|style).*?#s', '', $output); + $output = preg_replace('/sf-dump-\d+/', 'sf-dump', $output); + + $this->assertSame($xOutput, trim($output)); + $this->assertSame(1, $collector->getDumpsCount()); + $collector->serialize(); + } + + public function testFlush() + { + $data = new Data(array(array(456))); + $collector = new DumpDataCollector(); + $collector->dump($data); + $line = __LINE__ - 1; + + ob_start(); + $collector->__destruct(); + $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n456\n", ob_get_clean()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php new file mode 100644 index 00000000..afad9f58 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ExceptionDataCollectorTest extends TestCase +{ + public function testCollect() + { + $e = new \Exception('foo', 500); + $c = new ExceptionDataCollector(); + $flattened = FlattenException::create($e); + $trace = $flattened->getTrace(); + + $this->assertFalse($c->hasException()); + + $c->collect(new Request(), new Response(), $e); + + $this->assertTrue($c->hasException()); + $this->assertEquals($flattened, $c->getException()); + $this->assertSame('foo', $c->getMessage()); + $this->assertSame(500, $c->getCode()); + $this->assertSame('exception', $c->getName()); + $this->assertSame($trace, $c->getTrace()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.php new file mode 100644 index 00000000..62bf2c00 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.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\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; + +class LoggerDataCollectorTest extends TestCase +{ + public function testCollectWithUnexpectedFormat() + { + $logger = $this->getMockBuilder('Symfony\Component\HttpKernel\Log\DebugLoggerInterface')->getMock(); + $logger->expects($this->once())->method('countErrors')->will($this->returnValue('foo')); + $logger->expects($this->exactly(2))->method('getLogs')->will($this->returnValue(array())); + + $c = new LoggerDataCollector($logger, __DIR__.'/'); + $c->lateCollect(); + $compilerLogs = $c->getCompilerLogs()->getValue('message'); + + $this->assertSame(array( + array('message' => 'Removed service "Psr\Container\ContainerInterface"; reason: private alias.'), + array('message' => 'Removed service "Symfony\Component\DependencyInjection\ContainerInterface"; reason: private alias.'), + ), $compilerLogs['Symfony\Component\DependencyInjection\Compiler\RemovePrivateAliasesPass']); + + $this->assertSame(array( + array('message' => 'Some custom logging message'), + array('message' => 'With ending :'), + ), $compilerLogs['Unknown Compiler Pass']); + } + + /** + * @dataProvider getCollectTestData + */ + public function testCollect($nb, $logs, $expectedLogs, $expectedDeprecationCount, $expectedScreamCount, $expectedPriorities = null) + { + $logger = $this->getMockBuilder('Symfony\Component\HttpKernel\Log\DebugLoggerInterface')->getMock(); + $logger->expects($this->once())->method('countErrors')->will($this->returnValue($nb)); + $logger->expects($this->exactly(2))->method('getLogs')->will($this->returnValue($logs)); + + $c = new LoggerDataCollector($logger); + $c->lateCollect(); + + $this->assertEquals('logger', $c->getName()); + $this->assertEquals($nb, $c->countErrors()); + + $logs = array_map(function ($v) { + if (isset($v['context']['exception'])) { + $e = &$v['context']['exception']; + $e = isset($e["\0*\0message"]) ? array($e["\0*\0message"], $e["\0*\0severity"]) : array($e["\0Symfony\Component\Debug\Exception\SilencedErrorContext\0severity"]); + } + + return $v; + }, $c->getLogs()->getValue(true)); + $this->assertEquals($expectedLogs, $logs); + $this->assertEquals($expectedDeprecationCount, $c->countDeprecations()); + $this->assertEquals($expectedScreamCount, $c->countScreams()); + + if (isset($expectedPriorities)) { + $this->assertSame($expectedPriorities, $c->getPriorities()->getValue(true)); + } + } + + public function getCollectTestData() + { + yield 'simple log' => array( + 1, + array(array('message' => 'foo', 'context' => array(), 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo', 'context' => array(), 'priority' => 100, 'priorityName' => 'DEBUG')), + 0, + 0, + ); + + yield 'log with a context' => array( + 1, + array(array('message' => 'foo', 'context' => array('foo' => 'bar'), 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo', 'context' => array('foo' => 'bar'), 'priority' => 100, 'priorityName' => 'DEBUG')), + 0, + 0, + ); + + if (!class_exists(SilencedErrorContext::class)) { + return; + } + + yield 'logs with some deprecations' => array( + 1, + array( + array('message' => 'foo3', 'context' => array('exception' => new \ErrorException('warning', 0, E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo', 'context' => array('exception' => new \ErrorException('deprecated', 0, E_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo2', 'context' => array('exception' => new \ErrorException('deprecated', 0, E_USER_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG'), + ), + array( + array('message' => 'foo3', 'context' => array('exception' => array('warning', E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo', 'context' => array('exception' => array('deprecated', E_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), + array('message' => 'foo2', 'context' => array('exception' => array('deprecated', E_USER_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), + ), + 2, + 0, + array(100 => array('count' => 3, 'name' => 'DEBUG')), + ); + + yield 'logs with some silent errors' => array( + 1, + array( + array('message' => 'foo3', 'context' => array('exception' => new \ErrorException('warning', 0, E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo3', 'context' => array('exception' => new SilencedErrorContext(E_USER_WARNING, __FILE__, __LINE__)), 'priority' => 100, 'priorityName' => 'DEBUG'), + ), + array( + array('message' => 'foo3', 'context' => array('exception' => array('warning', E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo3', 'context' => array('exception' => array(E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => true), + ), + 0, + 1, + ); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php new file mode 100644 index 00000000..ab78e9e8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class MemoryDataCollectorTest extends TestCase +{ + public function testCollect() + { + $collector = new MemoryDataCollector(); + $collector->collect(new Request(), new Response()); + + $this->assertInternalType('integer', $collector->getMemory()); + $this->assertInternalType('integer', $collector->getMemoryLimit()); + $this->assertSame('memory', $collector->getName()); + } + + /** @dataProvider getBytesConversionTestData */ + public function testBytesConversion($limit, $bytes) + { + $collector = new MemoryDataCollector(); + $method = new \ReflectionMethod($collector, 'convertToBytes'); + $method->setAccessible(true); + $this->assertEquals($bytes, $method->invoke($collector, $limit)); + } + + public function getBytesConversionTestData() + { + return array( + array('2k', 2048), + array('2 k', 2048), + array('8m', 8 * 1024 * 1024), + array('+2 k', 2048), + array('+2???k', 2048), + array('0x10', 16), + array('0xf', 15), + array('010', 8), + array('+0x10 k', 16 * 1024), + array('1g', 1024 * 1024 * 1024), + array('1G', 1024 * 1024 * 1024), + array('-1', -1), + array('0', 0), + array('2mk', 2048), // the unit must be the last char, so in this case 'k', not 'm' + ); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php new file mode 100644 index 00000000..93767b96 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class RequestDataCollectorTest extends TestCase +{ + public function testCollect() + { + $c = new RequestDataCollector(); + + $c->collect($request = $this->createRequest(), $this->createResponse()); + $c->lateCollect(); + + $attributes = $c->getRequestAttributes(); + + $this->assertSame('request', $c->getName()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestHeaders()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestServer()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestCookies()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $attributes); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestRequest()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestQuery()); + $this->assertInstanceOf(ParameterBag::class, $c->getResponseCookies()); + $this->assertSame('html', $c->getFormat()); + $this->assertEquals('foobar', $c->getRoute()); + $this->assertEquals(array('name' => 'foo'), $c->getRouteParams()); + $this->assertSame(array(), $c->getSessionAttributes()); + $this->assertSame('en', $c->getLocale()); + $this->assertContains(__FILE__, $attributes->get('resource')); + $this->assertSame('stdClass', $attributes->get('object')->getType()); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getResponseHeaders()); + $this->assertSame('OK', $c->getStatusText()); + $this->assertSame(200, $c->getStatusCode()); + $this->assertSame('application/json', $c->getContentType()); + } + + public function testCollectWithoutRouteParams() + { + $request = $this->createRequest(array()); + + $c = new RequestDataCollector(); + $c->collect($request, $this->createResponse()); + $c->lateCollect(); + + $this->assertEquals(array(), $c->getRouteParams()); + } + + public function testKernelResponseDoesNotStartSession() + { + $kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock(); + $request = new Request(); + $session = new Session(new MockArraySessionStorage()); + $request->setSession($session); + $response = new Response(); + + $c = new RequestDataCollector(); + $c->onKernelResponse(new FilterResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response)); + + $this->assertFalse($session->isStarted()); + } + + /** + * @dataProvider provideControllerCallables + */ + public function testControllerInspection($name, $callable, $expected) + { + $c = new RequestDataCollector(); + $request = $this->createRequest(); + $response = $this->createResponse(); + $this->injectController($c, $callable, $request); + $c->collect($request, $response); + $c->lateCollect(); + + $this->assertSame($expected, $c->getController()->getValue(true), sprintf('Testing: %s', $name)); + } + + public function provideControllerCallables() + { + // make sure we always match the line number + $r1 = new \ReflectionMethod($this, 'testControllerInspection'); + $r2 = new \ReflectionMethod($this, 'staticControllerMethod'); + $r3 = new \ReflectionClass($this); + + // test name, callable, expected + return array( + array( + '"Regular" callable', + array($this, 'testControllerInspection'), + array( + 'class' => __NAMESPACE__.'\RequestDataCollectorTest', + 'method' => 'testControllerInspection', + 'file' => __FILE__, + 'line' => $r1->getStartLine(), + ), + ), + + array( + 'Closure', + function () { return 'foo'; }, + array( + 'class' => __NAMESPACE__.'\{closure}', + 'method' => null, + 'file' => __FILE__, + 'line' => __LINE__ - 5, + ), + ), + + array( + 'Static callback as string', + __NAMESPACE__.'\RequestDataCollectorTest::staticControllerMethod', + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine(), + ), + ), + + array( + 'Static callable with instance', + array($this, 'staticControllerMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine(), + ), + ), + + array( + 'Static callable with class name', + array('Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', 'staticControllerMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine(), + ), + ), + + array( + 'Callable with instance depending on __call()', + array($this, 'magicMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'magicMethod', + 'file' => 'n/a', + 'line' => 'n/a', + ), + ), + + array( + 'Callable with class name depending on __callStatic()', + array('Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', 'magicMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'magicMethod', + 'file' => 'n/a', + 'line' => 'n/a', + ), + ), + + array( + 'Invokable controller', + $this, + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => null, + 'file' => __FILE__, + 'line' => $r3->getStartLine(), + ), + ), + ); + } + + public function testItIgnoresInvalidCallables() + { + $request = $this->createRequestWithSession(); + $response = new RedirectResponse('/'); + + $c = new RequestDataCollector(); + $c->collect($request, $response); + + $this->assertSame('n/a', $c->getController()); + } + + protected function createRequest($routeParams = array('name' => 'foo')) + { + $request = Request::create('http://test.com/foo?bar=baz'); + $request->attributes->set('foo', 'bar'); + $request->attributes->set('_route', 'foobar'); + $request->attributes->set('_route_params', $routeParams); + $request->attributes->set('resource', fopen(__FILE__, 'r')); + $request->attributes->set('object', new \stdClass()); + + return $request; + } + + private function createRequestWithSession() + { + $request = $this->createRequest(); + $request->attributes->set('_controller', 'Foo::bar'); + $request->setSession(new Session(new MockArraySessionStorage())); + $request->getSession()->start(); + + return $request; + } + + protected function createResponse() + { + $response = new Response(); + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'application/json'); + $response->headers->set('X-Foo-Bar', null); + $response->headers->setCookie(new Cookie('foo', 'bar', 1, '/foo', 'localhost', true, true)); + $response->headers->setCookie(new Cookie('bar', 'foo', new \DateTime('@946684800'))); + $response->headers->setCookie(new Cookie('bazz', 'foo', '2000-12-12')); + + return $response; + } + + /** + * Inject the given controller callable into the data collector. + */ + protected function injectController($collector, $controller, $request) + { + $resolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface')->getMock(); + $httpKernel = new HttpKernel(new EventDispatcher(), $resolver, null, $this->getMockBuilder(ArgumentResolverInterface::class)->getMock()); + $event = new FilterControllerEvent($httpKernel, $controller, $request, HttpKernelInterface::MASTER_REQUEST); + $collector->onKernelController($event); + } + + /** + * Dummy method used as controller callable. + */ + public static function staticControllerMethod() + { + throw new \LogicException('Unexpected method call'); + } + + /** + * Magic method to allow non existing methods to be called and delegated. + */ + public function __call($method, $args) + { + throw new \LogicException('Unexpected method call'); + } + + /** + * Magic method to allow non existing methods to be called and delegated. + */ + public static function __callStatic($method, $args) + { + throw new \LogicException('Unexpected method call'); + } + + public function __invoke() + { + throw new \LogicException('Unexpected method call'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php new file mode 100644 index 00000000..8048cc37 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class TimeDataCollectorTest extends TestCase +{ + public function testCollect() + { + $c = new TimeDataCollector(); + + $request = new Request(); + $request->server->set('REQUEST_TIME', 1); + + $c->collect($request, new Response()); + + $this->assertEquals(0, $c->getStartTime()); + + $request->server->set('REQUEST_TIME_FLOAT', 2); + + $c->collect($request, new Response()); + + $this->assertEquals(2000, $c->getStartTime()); + + $request = new Request(); + $c->collect($request, new Response()); + $this->assertEquals(0, $c->getStartTime()); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); + $kernel->expects($this->once())->method('getStartTime')->will($this->returnValue(123456)); + + $c = new TimeDataCollector($kernel); + $request = new Request(); + $request->server->set('REQUEST_TIME', 1); + + $c->collect($request, new Response()); + $this->assertEquals(123456000, $c->getStartTime()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.php new file mode 100644 index 00000000..5fe92d60 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.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\HttpKernel\Tests\DataCollector\Util; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; + +/** + * @group legacy + */ +class ValueExporterTest extends TestCase +{ + /** + * @var ValueExporter + */ + private $valueExporter; + + protected function setUp() + { + $this->valueExporter = new ValueExporter(); + } + + public function testDateTime() + { + $dateTime = new \DateTime('2014-06-10 07:35:40', new \DateTimeZone('UTC')); + $this->assertSame('Object(DateTime) - 2014-06-10T07:35:40+00:00', $this->valueExporter->exportValue($dateTime)); + } + + public function testDateTimeImmutable() + { + $dateTime = new \DateTimeImmutable('2014-06-10 07:35:40', new \DateTimeZone('UTC')); + $this->assertSame('Object(DateTimeImmutable) - 2014-06-10T07:35:40+00:00', $this->valueExporter->exportValue($dateTime)); + } + + public function testIncompleteClass() + { + $foo = new \__PHP_Incomplete_Class(); + $array = new \ArrayObject($foo); + $array['__PHP_Incomplete_Class_Name'] = 'AppBundle/Foo'; + $this->assertSame('__PHP_Incomplete_Class(AppBundle/Foo)', $this->valueExporter->exportValue($foo)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php b/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.php new file mode 100644 index 00000000..d616098a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Debug/FileLinkFormatterTest.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\HttpKernel\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; + +class FileLinkFormatterTest extends TestCase +{ + public function testWhenNoFileLinkFormatAndNoRequest() + { + $sut = new FileLinkFormatter(); + + $this->assertFalse($sut->format('/kernel/root/src/my/very/best/file.php', 3)); + } + + public function testWhenFileLinkFormatAndNoRequest() + { + $file = __DIR__.DIRECTORY_SEPARATOR.'file.php'; + + $sut = new FileLinkFormatter('debug://open?url=file://%f&line=%l', new RequestStack()); + + $this->assertSame("debug://open?url=file://$file&line=3", $sut->format($file, 3)); + } + + public function testWhenFileLinkFormatAndRequest() + { + $file = __DIR__.DIRECTORY_SEPARATOR.'file.php'; + $baseDir = __DIR__; + $requestStack = new RequestStack(); + $request = new Request(); + $requestStack->push($request); + + $sut = new FileLinkFormatter('debug://open?url=file://%f&line=%l', $requestStack, __DIR__, '/_profiler/open?file=%f&line=%l#line%l'); + + $this->assertSame("debug://open?url=file://$file&line=3", $sut->format($file, 3)); + } + + public function testWhenNoFileLinkFormatAndRequest() + { + $file = __DIR__.DIRECTORY_SEPARATOR.'file.php'; + $requestStack = new RequestStack(); + $request = new Request(); + $requestStack->push($request); + + $request->server->set('SERVER_NAME', 'www.example.org'); + $request->server->set('SERVER_PORT', 80); + $request->server->set('SCRIPT_NAME', '/app.php'); + $request->server->set('SCRIPT_FILENAME', '/web/app.php'); + $request->server->set('REQUEST_URI', '/app.php/example'); + + $sut = new FileLinkFormatter(null, $requestStack, __DIR__, '/_profiler/open?file=%f&line=%l#line%l'); + + $this->assertSame('http://www.example.org/app.php/_profiler/open?file=file.php&line=3#line3', $sut->format($file, 3)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php b/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php new file mode 100644 index 00000000..aaa82d52 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Debug; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; + +class TraceableEventDispatcherTest extends TestCase +{ + public function testStopwatchSections() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch = new Stopwatch()); + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $response = $kernel->handle($request); + $kernel->terminate($request, $response); + + $events = $stopwatch->getSectionEvents($response->headers->get('X-Debug-Token')); + $this->assertEquals(array( + '__section__', + 'kernel.request', + 'kernel.controller', + 'kernel.controller_arguments', + 'controller', + 'kernel.response', + 'kernel.terminate', + ), array_keys($events)); + } + + public function testStopwatchCheckControllerOnRequestEvent() + { + $stopwatch = $this->getMockBuilder('Symfony\Component\Stopwatch\Stopwatch') + ->setMethods(array('isStarted')) + ->getMock(); + $stopwatch->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(false)); + + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); + + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $kernel->handle($request); + } + + public function testStopwatchStopControllerOnRequestEvent() + { + $stopwatch = $this->getMockBuilder('Symfony\Component\Stopwatch\Stopwatch') + ->setMethods(array('isStarted', 'stop', 'stopSection')) + ->getMock(); + $stopwatch->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)); + $stopwatch->expects($this->once()) + ->method('stop'); + $stopwatch->expects($this->once()) + ->method('stopSection'); + + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); + + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $kernel->handle($request); + } + + public function testAddListenerNested() + { + $called1 = false; + $called2 = false; + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $dispatcher->addListener('my-event', function () use ($dispatcher, &$called1, &$called2) { + $called1 = true; + $dispatcher->addListener('my-event', function () use (&$called2) { + $called2 = true; + }); + }); + $dispatcher->dispatch('my-event'); + $this->assertTrue($called1); + $this->assertFalse($called2); + $dispatcher->dispatch('my-event'); + $this->assertTrue($called2); + } + + public function testListenerCanRemoveItselfWhenExecuted() + { + $eventDispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $listener1 = function () use ($eventDispatcher, &$listener1) { + $eventDispatcher->removeListener('foo', $listener1); + }; + $eventDispatcher->addListener('foo', $listener1); + $eventDispatcher->addListener('foo', function () {}); + $eventDispatcher->dispatch('foo'); + + $this->assertCount(1, $eventDispatcher->getListeners('foo'), 'expected listener1 to be removed'); + } + + protected function getHttpKernel($dispatcher, $controller) + { + $controllerResolver = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface')->getMock(); + $controllerResolver->expects($this->once())->method('getController')->will($this->returnValue($controller)); + $argumentResolver = $this->getMockBuilder('Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface')->getMock(); + $argumentResolver->expects($this->once())->method('getArguments')->will($this->returnValue(array())); + + return new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.php new file mode 100644 index 00000000..a2fb6afc --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/AddAnnotatedClassesToCachePassTest.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\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; + +class AddAnnotatedClassesToCachePassTest extends TestCase +{ + public function testExpandClasses() + { + $r = new \ReflectionClass(AddAnnotatedClassesToCachePass::class); + $pass = $r->newInstanceWithoutConstructor(); + $r = new \ReflectionMethod(AddAnnotatedClassesToCachePass::class, 'expandClasses'); + $r->setAccessible(true); + $expand = $r->getClosure($pass); + + $this->assertSame('Foo', $expand(array('Foo'), array())[0]); + $this->assertSame('Foo', $expand(array('\\Foo'), array())[0]); + $this->assertSame('Foo', $expand(array('Foo'), array('\\Foo'))[0]); + $this->assertSame('Foo', $expand(array('Foo'), array('Foo'))[0]); + $this->assertSame('Foo', $expand(array('\\Foo'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo', $expand(array('Foo'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo', $expand(array('\\Foo'), array('\\Foo\\Bar\\Acme'))[0]); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo\\Bar\\Acme', $expand(array('Foo\\'), array('\\Foo\\Bar\\Acme'))[0]); + $this->assertEmpty($expand(array('Foo\\'), array('\\Foo'))); + + $this->assertSame('Acme\\Foo\\Bar', $expand(array('**\\Foo\\'), array('\\Acme\\Foo\\Bar'))[0]); + $this->assertEmpty($expand(array('**\\Foo\\'), array('\\Foo\\Bar'))); + $this->assertEmpty($expand(array('**\\Foo\\'), array('\\Acme\\Foo'))); + $this->assertEmpty($expand(array('**\\Foo\\'), array('\\Foo'))); + + $this->assertSame('Acme\\Foo', $expand(array('**\\Foo'), array('\\Acme\\Foo'))[0]); + $this->assertEmpty($expand(array('**\\Foo'), array('\\Acme\\Foo\\AcmeBundle'))); + $this->assertEmpty($expand(array('**\\Foo'), array('\\Acme\\FooBar\\AcmeBundle'))); + + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\*\\Bar'), array('\\Foo\\Acme\\Bar'))[0]); + $this->assertEmpty($expand(array('Foo\\*\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))); + + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**\\Bar'), array('\\Foo\\Acme\\Bar'))[0]); + $this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(array('Foo\\**\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))[0]); + + $this->assertSame('Acme\\Bar', $expand(array('*\\Bar'), array('\\Acme\\Bar'))[0]); + $this->assertEmpty($expand(array('*\\Bar'), array('\\Bar'))); + $this->assertEmpty($expand(array('*\\Bar'), array('\\Foo\\Acme\\Bar'))); + + $this->assertSame('Foo\\Acme\\Bar', $expand(array('**\\Bar'), array('\\Foo\\Acme\\Bar'))[0]); + $this->assertSame('Foo\\Acme\\Bundle\\Bar', $expand(array('**\\Bar'), array('\\Foo\\Acme\\Bundle\\Bar'))[0]); + $this->assertEmpty($expand(array('**\\Bar'), array('\\Bar'))); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\*'), array('\\Foo\\Bar'))[0]); + $this->assertEmpty($expand(array('Foo\\*'), array('\\Foo\\Acme\\Bar'))); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Bar'))[0]); + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Acme\\Bar'))[0]); + + $this->assertSame(array('Foo\\Bar'), $expand(array('Foo\\*'), array('Foo\\Bar', 'Foo\\BarTest'))); + $this->assertSame(array('Foo\\Bar', 'Foo\\BarTest'), $expand(array('Foo\\*', 'Foo\\*Test'), array('Foo\\Bar', 'Foo\\BarTest'))); + + $this->assertSame( + 'Acme\\FooBundle\\Controller\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Acme\\FooBundle\\Controller\\DefaultController'))[0] + ); + + $this->assertSame( + 'FooBundle\\Controller\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\FooBundle\\Controller\\DefaultController'))[0] + ); + + $this->assertSame( + 'Acme\\FooBundle\\Controller\\Bar\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Acme\\FooBundle\\Controller\\Bar\\DefaultController'))[0] + ); + + $this->assertSame( + 'Bundle\\Controller\\Bar\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Bundle\\Controller\\Bar\\DefaultController'))[0] + ); + + $this->assertSame( + 'Acme\\Bundle\\Controller\\Bar\\DefaultController', + $expand(array('**Bundle\\Controller\\'), array('\\Acme\\Bundle\\Controller\\Bar\\DefaultController'))[0] + ); + + $this->assertSame('Foo\\Bar', $expand(array('Foo\\Bar'), array())[0]); + $this->assertSame('Foo\\Acme\\Bar', $expand(array('Foo\\**'), array('\\Foo\\Acme\\Bar'))[0]); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php new file mode 100644 index 00000000..df8977de --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.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\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; + +class ControllerArgumentValueResolverPassTest extends TestCase +{ + public function testServicesAreOrderedAccordingToPriority() + { + $services = array( + 'n3' => array(array()), + 'n1' => array(array('priority' => 200)), + 'n2' => array(array('priority' => 100)), + ); + + $expected = array( + new Reference('n1'), + new Reference('n2'), + new Reference('n3'), + ); + + $definition = new Definition(ArgumentResolver::class, array(null, array())); + $container = new ContainerBuilder(); + $container->setDefinition('argument_resolver', $definition); + + foreach ($services as $id => list($tag)) { + $container->register($id)->addTag('controller.argument_value_resolver', $tag); + } + + (new ControllerArgumentValueResolverPass())->process($container); + $this->assertEquals($expected, $definition->getArgument(1)->getValues()); + } + + public function testReturningEmptyArrayWhenNoService() + { + $definition = new Definition(ArgumentResolver::class, array(null, array())); + $container = new ContainerBuilder(); + $container->setDefinition('argument_resolver', $definition); + + (new ControllerArgumentValueResolverPass())->process($container); + $this->assertEquals(array(), $definition->getArgument(1)->getValues()); + } + + public function testNoArgumentResolver() + { + $container = new ContainerBuilder(); + + (new ControllerArgumentValueResolverPass())->process($container); + + $this->assertFalse($container->hasDefinition('argument_resolver')); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php new file mode 100644 index 00000000..d28c6eca --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +class FragmentRendererPassTest extends TestCase +{ + /** + * Tests that content rendering not implementing FragmentRendererInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testContentRendererWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_content_renderer' => array(array('alias' => 'foo')), + ); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.fragment_renderer here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $pass = new FragmentRendererPass(); + $pass->process($builder); + } + + public function testValidContentRenderer() + { + $services = array( + 'my_content_renderer' => array(array('alias' => 'foo')), + ); + + $renderer = new Definition('', array(null)); + + $definition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService')); + + $builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'getReflectionClass'))->getMock(); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.fragment_renderer here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->onConsecutiveCalls($renderer, $definition)); + + $builder->expects($this->atLeastOnce()) + ->method('getReflectionClass') + ->with('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService') + ->will($this->returnValue(new \ReflectionClass('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService'))); + + $pass = new FragmentRendererPass(); + $pass->process($builder); + + $this->assertInstanceOf(Reference::class, $renderer->getArgument(0)); + } +} + +class RendererService implements FragmentRendererInterface +{ + public function render($uri, Request $request = null, array $options = array()) + { + } + + public function getName() + { + return 'test'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php new file mode 100644 index 00000000..0406345d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class LazyLoadingFragmentHandlerTest extends TestCase +{ + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler::addRendererService() method is deprecated since version 3.3 and will be removed in 4.0. + */ + public function testRenderWithLegacyMapping() + { + $renderer = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface')->getMock(); + $renderer->expects($this->once())->method('getName')->will($this->returnValue('foo')); + $renderer->expects($this->any())->method('render')->will($this->returnValue(new Response())); + + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); + + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); + $container->expects($this->once())->method('get')->will($this->returnValue($renderer)); + + $handler = new LazyLoadingFragmentHandler($container, $requestStack, false); + $handler->addRendererService('foo', 'foo'); + + $handler->render('/foo', 'foo'); + + // second call should not lazy-load anymore (see once() above on the get() method) + $handler->render('/foo', 'foo'); + } + + public function testRender() + { + $renderer = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface')->getMock(); + $renderer->expects($this->once())->method('getName')->will($this->returnValue('foo')); + $renderer->expects($this->any())->method('render')->will($this->returnValue(new Response())); + + $requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $requestStack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); + + $container = $this->getMockBuilder('Psr\Container\ContainerInterface')->getMock(); + $container->expects($this->once())->method('has')->with('foo')->willReturn(true); + $container->expects($this->once())->method('get')->will($this->returnValue($renderer)); + + $handler = new LazyLoadingFragmentHandler($container, $requestStack, false); + + $handler->render('/foo', 'foo'); + + // second call should not lazy-load anymore (see once() above on the get() method) + $handler->render('/foo', 'foo'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php new file mode 100644 index 00000000..81fc8b45 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; + +class MergeExtensionConfigurationPassTest extends TestCase +{ + public function testAutoloadMainExtension() + { + $container = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ContainerBuilder')->setMethods(array('getExtensionConfig', 'loadFromExtension', 'getParameterBag', 'getDefinitions', 'getAliases', 'getExtensions'))->getMock(); + $params = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag')->getMock(); + + $container->expects($this->at(0)) + ->method('getExtensionConfig') + ->with('loaded') + ->will($this->returnValue(array(array()))); + $container->expects($this->at(1)) + ->method('getExtensionConfig') + ->with('notloaded') + ->will($this->returnValue(array())); + $container->expects($this->once()) + ->method('loadFromExtension') + ->with('notloaded', array()); + + $container->expects($this->any()) + ->method('getParameterBag') + ->will($this->returnValue($params)); + $params->expects($this->any()) + ->method('all') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getDefinitions') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getAliases') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getExtensions') + ->will($this->returnValue(array())); + + $configPass = new MergeExtensionConfigurationPass(array('loaded', 'notloaded')); + $configPass->process($container); + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php new file mode 100644 index 00000000..0542698d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/RegisterControllerArgumentLocatorsPassTest.php @@ -0,0 +1,329 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerAwareTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; + +class RegisterControllerArgumentLocatorsPassTest extends TestCase +{ + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Class "Symfony\Component\HttpKernel\Tests\DependencyInjection\NotFound" used for service "foo" cannot be found. + */ + public function testInvalidClass() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NotFound::class) + ->addTag('controller.service_arguments') + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "action" attribute on tag "controller.service_arguments" {"argument":"bar"} for service "foo". + */ + public function testNoAction() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('argument' => 'bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "argument" attribute on tag "controller.service_arguments" {"action":"fooAction"} for service "foo". + */ + public function testNoArgument() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Missing "id" attribute on tag "controller.service_arguments" {"action":"fooAction","argument":"bar"} for service "foo". + */ + public function testNoService() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid "action" attribute on tag "controller.service_arguments" for service "foo": no public "barAction()" method found on class "Symfony\Component\HttpKernel\Tests\DependencyInjection\RegisterTestController". + */ + public function testInvalidMethod() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'barAction', 'argument' => 'bar', 'id' => 'bar_service')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Invalid "controller.service_arguments" tag for service "foo": method "fooAction()" has no "baz" argument on class "Symfony\Component\HttpKernel\Tests\DependencyInjection\RegisterTestController". + */ + public function testInvalidArgument() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'baz', 'id' => 'bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + public function testAllActions() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertEquals(array('foo:fooAction'), array_keys($locator)); + $this->assertInstanceof(ServiceClosureArgument::class, $locator['foo:fooAction']); + + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); + + $this->assertSame(ServiceLocator::class, $locator->getClass()); + $this->assertFalse($locator->isPublic()); + + $expected = array('bar' => new ServiceClosureArgument(new TypedReference(ControllerDummy::class, ControllerDummy::class, RegisterTestController::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE))); + $this->assertEquals($expected, $locator->getArgument(0)); + } + + public function testExplicitArgument() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => 'bar')) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => 'baz')) // should be ignored, the first wins + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); + + $expected = array('bar' => new ServiceClosureArgument(new TypedReference('bar', ControllerDummy::class, RegisterTestController::class))); + $this->assertEquals($expected, $locator->getArgument(0)); + } + + public function testOptionalArgument() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', RegisterTestController::class) + ->addTag('controller.service_arguments', array('action' => 'fooAction', 'argument' => 'bar', 'id' => '?bar')) + ; + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $locator = $container->getDefinition((string) $locator['foo:fooAction']->getValues()[0]); + + $expected = array('bar' => new ServiceClosureArgument(new TypedReference('bar', ControllerDummy::class, RegisterTestController::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE))); + $this->assertEquals($expected, $locator->getArgument(0)); + } + + public function testSkipSetContainer() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', ContainerAwareRegisterTestController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertSame(array('foo:fooAction'), array_keys($locator)); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Cannot determine controller argument for "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClassController::fooAction()": the $nonExistent argument is type-hinted with the non-existent class or interface: "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClass". Did you forget to add a use statement? + */ + public function testExceptionOnNonExistentTypeHint() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NonExistentClassController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + * @expectedExceptionMessage Cannot determine controller argument for "Symfony\Component\HttpKernel\Tests\DependencyInjection\NonExistentClassDifferentNamespaceController::fooAction()": the $nonExistent argument is type-hinted with the non-existent class or interface: "Acme\NonExistentClass". + */ + public function testExceptionOnNonExistentTypeHintDifferentNamespace() + { + $container = new ContainerBuilder(); + $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NonExistentClassDifferentNamespaceController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + } + + public function testNoExceptionOnNonExistentTypeHintOptionalArg() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', NonExistentClassOptionalController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertSame(array('foo:barAction', 'foo:fooAction'), array_keys($locator)); + } + + public function testArgumentWithNoTypeHintIsOk() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('foo', ArgumentWithoutTypeController::class) + ->addTag('controller.service_arguments'); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $locator = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + $this->assertEmpty(array_keys($locator)); + } +} + +class RegisterTestController +{ + public function __construct(ControllerDummy $bar) + { + } + + public function fooAction(ControllerDummy $bar) + { + } + + protected function barAction(ControllerDummy $bar) + { + } +} + +class ContainerAwareRegisterTestController implements ContainerAwareInterface +{ + use ContainerAwareTrait; + + public function fooAction(ControllerDummy $bar) + { + } +} + +class ControllerDummy +{ +} + +class NonExistentClassController +{ + public function fooAction(NonExistentClass $nonExistent) + { + } +} + +class NonExistentClassDifferentNamespaceController +{ + public function fooAction(\Acme\NonExistentClass $nonExistent) + { + } +} + +class NonExistentClassOptionalController +{ + public function fooAction(NonExistentClass $nonExistent = null) + { + } + + public function barAction(NonExistentClass $nonExistent = null, $bar) + { + } +} + +class ArgumentWithoutTypeController +{ + public function fooAction($someArg) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php b/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php new file mode 100644 index 00000000..9cac9681 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPassTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\ResolveInvalidReferencesPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; +use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; + +class RemoveEmptyControllerArgumentLocatorsPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('stdClass', 'stdClass'); + $container->register(parent::class, 'stdClass'); + $container->register('c1', RemoveTestController1::class)->addTag('controller.service_arguments'); + $container->register('c2', RemoveTestController2::class)->addTag('controller.service_arguments') + ->addMethodCall('setTestCase', array(new Reference('c1'))); + + $pass = new RegisterControllerArgumentLocatorsPass(); + $pass->process($container); + + $controllers = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertCount(2, $container->getDefinition((string) $controllers['c1:fooAction']->getValues()[0])->getArgument(0)); + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:setTestCase']->getValues()[0])->getArgument(0)); + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:fooAction']->getValues()[0])->getArgument(0)); + + (new ResolveInvalidReferencesPass())->process($container); + + $this->assertCount(1, $container->getDefinition((string) $controllers['c2:setTestCase']->getValues()[0])->getArgument(0)); + $this->assertSame(array(), $container->getDefinition((string) $controllers['c2:fooAction']->getValues()[0])->getArgument(0)); + + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $controllers = $container->getDefinition((string) $resolver->getArgument(0))->getArgument(0); + + $this->assertSame(array('c1:fooAction'), array_keys($controllers)); + $this->assertSame(array('bar'), array_keys($container->getDefinition((string) $controllers['c1:fooAction']->getValues()[0])->getArgument(0))); + + $expectedLog = array( + 'Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass: Removing service-argument resolver for controller "c2:fooAction": no corresponding services exist for the referenced types.', + 'Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass: Removing method "setTestCase" of service "c2" from controller candidates: the method is called at instantiation, thus cannot be an action.', + ); + + $this->assertSame($expectedLog, $container->getCompiler()->getLog()); + } + + public function testSameIdClass() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register(RegisterTestController::class, RegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $expected = array( + RegisterTestController::class.':fooAction', + RegisterTestController::class.'::fooAction', + ); + $this->assertEquals($expected, array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0))); + } + + public function testInvoke() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register('invokable', InvokableRegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $this->assertEquals( + array('invokable:__invoke', 'invokable'), + array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0)) + ); + } + + public function testInvokeSameIdClass() + { + $container = new ContainerBuilder(); + $resolver = $container->register('argument_resolver.service')->addArgument(array()); + + $container->register(InvokableRegisterTestController::class, InvokableRegisterTestController::class) + ->addTag('controller.service_arguments') + ; + + (new RegisterControllerArgumentLocatorsPass())->process($container); + (new RemoveEmptyControllerArgumentLocatorsPass())->process($container); + + $expected = array( + InvokableRegisterTestController::class.':__invoke', + InvokableRegisterTestController::class.'::__invoke', + InvokableRegisterTestController::class, + ); + $this->assertEquals($expected, array_keys($container->getDefinition((string) $resolver->getArgument(0))->getArgument(0))); + } +} + +class RemoveTestController1 +{ + public function fooAction(\stdClass $bar, ClassNotInContainer $baz) + { + } +} + +class RemoveTestController2 +{ + public function setTestCase(TestCase $test) + { + } + + public function fooAction(ClassNotInContainer $bar) + { + } +} + +class InvokableRegisterTestController +{ + public function __invoke(\stdClass $bar) + { + } +} + +class ClassNotInContainer +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php b/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.php new file mode 100644 index 00000000..72425793 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Event/GetResponseForExceptionEventTest.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\HttpKernel\Tests\Event; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Tests\TestHttpKernel; + +class GetResponseForExceptionEventTest extends TestCase +{ + public function testAllowSuccessfulResponseIsFalseByDefault() + { + $event = new GetResponseForExceptionEvent(new TestHttpKernel(), new Request(), 1, new \Exception()); + + $this->assertFalse($event->isAllowingCustomResponseCode()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php new file mode 100644 index 00000000..f4878f62 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.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\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Test AddRequestFormatsListener class. + * + * @author Gildas Quemener + */ +class AddRequestFormatsListenerTest extends TestCase +{ + /** + * @var AddRequestFormatsListener + */ + private $listener; + + protected function setUp() + { + $this->listener = new AddRequestFormatsListener(array('csv' => array('text/csv', 'text/plain'))); + } + + protected function tearDown() + { + $this->listener = null; + } + + public function testIsAnEventSubscriber() + { + $this->assertInstanceOf('Symfony\Component\EventDispatcher\EventSubscriberInterface', $this->listener); + } + + public function testRegisteredEvent() + { + $this->assertEquals( + array(KernelEvents::REQUEST => array('onKernelRequest', 1)), + AddRequestFormatsListener::getSubscribedEvents() + ); + } + + public function testSetAdditionalFormats() + { + $request = $this->getRequestMock(); + $event = $this->getGetResponseEventMock($request); + + $request->expects($this->once()) + ->method('setFormat') + ->with('csv', array('text/csv', 'text/plain')); + + $this->listener->onKernelRequest($event); + } + + protected function getRequestMock() + { + return $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock(); + } + + protected function getGetResponseEventMock(Request $request) + { + $event = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + + $event->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)); + + return $event; + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php new file mode 100644 index 00000000..d1349906 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * DebugHandlersListenerTest. + * + * @author Nicolas Grekas + */ +class DebugHandlersListenerTest extends TestCase +{ + public function testConfigure() + { + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $userHandler = function () {}; + $listener = new DebugHandlersListener($userHandler, $logger); + $xHandler = new ExceptionHandler(); + $eHandler = new ErrorHandler(); + $eHandler->setExceptionHandler(array($xHandler, 'handle')); + + $exception = null; + set_error_handler(array($eHandler, 'handleError')); + set_exception_handler(array($eHandler, 'handleException')); + try { + $listener->configure(); + } catch (\Exception $exception) { + } + restore_exception_handler(); + restore_error_handler(); + + if (null !== $exception) { + throw $exception; + } + + $this->assertSame($userHandler, $xHandler->setHandler('var_dump')); + + $loggers = $eHandler->setLoggers(array()); + + $this->assertArrayHasKey(E_DEPRECATED, $loggers); + $this->assertSame(array($logger, LogLevel::INFO), $loggers[E_DEPRECATED]); + } + + public function testConfigureForHttpKernelWithNoTerminateWithException() + { + $listener = new DebugHandlersListener(null); + $eHandler = new ErrorHandler(); + $event = new KernelEvent( + $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), + Request::create('/'), + HttpKernelInterface::MASTER_REQUEST + ); + + $exception = null; + $h = set_exception_handler(array($eHandler, 'handleException')); + try { + $listener->configure($event); + } catch (\Exception $exception) { + } + restore_exception_handler(); + + if (null !== $exception) { + throw $exception; + } + + $this->assertNull($h); + } + + public function testConsoleEvent() + { + $dispatcher = new EventDispatcher(); + $listener = new DebugHandlersListener(null); + $app = $this->getMockBuilder('Symfony\Component\Console\Application')->getMock(); + $app->expects($this->once())->method('getHelperSet')->will($this->returnValue(new HelperSet())); + $command = new Command(__FUNCTION__); + $command->setApplication($app); + $event = new ConsoleEvent($command, new ArgvInput(), new ConsoleOutput()); + + $dispatcher->addSubscriber($listener); + + $xListeners = array( + KernelEvents::REQUEST => array(array($listener, 'configure')), + ConsoleEvents::COMMAND => array(array($listener, 'configure')), + ); + $this->assertSame($xListeners, $dispatcher->getListeners()); + + $exception = null; + $eHandler = new ErrorHandler(); + set_error_handler(array($eHandler, 'handleError')); + set_exception_handler(array($eHandler, 'handleException')); + try { + $dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + } catch (\Exception $exception) { + } + restore_exception_handler(); + restore_error_handler(); + + if (null !== $exception) { + throw $exception; + } + + $xHandler = $eHandler->setExceptionHandler('var_dump'); + $this->assertInstanceOf('Closure', $xHandler); + + $app->expects($this->once()) + ->method('renderException'); + + $xHandler(new \Exception()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.php new file mode 100644 index 00000000..509f4430 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.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\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\HttpKernel\EventListener\DumpListener; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\VarDumper; + +/** + * DumpListenerTest. + * + * @author Nicolas Grekas + */ +class DumpListenerTest extends TestCase +{ + public function testSubscribedEvents() + { + $this->assertSame( + array(ConsoleEvents::COMMAND => array('configure', 1024)), + DumpListener::getSubscribedEvents() + ); + } + + public function testConfigure() + { + $prevDumper = VarDumper::setHandler('var_dump'); + VarDumper::setHandler($prevDumper); + + $cloner = new MockCloner(); + $dumper = new MockDumper(); + + ob_start(); + $exception = null; + $listener = new DumpListener($cloner, $dumper); + + try { + $listener->configure(); + + VarDumper::dump('foo'); + VarDumper::dump('bar'); + + $this->assertSame('+foo-+bar-', ob_get_clean()); + } catch (\Exception $exception) { + } + + VarDumper::setHandler($prevDumper); + + if (null !== $exception) { + throw $exception; + } + } +} + +class MockCloner implements ClonerInterface +{ + public function cloneVar($var) + { + return new Data(array(array($var.'-'))); + } +} + +class MockDumper implements DataDumperInterface +{ + public function dump(Data $data) + { + echo '+'.$data->getValue(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php new file mode 100644 index 00000000..f5501a3f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\EventListener\ExceptionListener; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Logger; + +/** + * ExceptionListenerTest. + * + * @author Robert Schönthal + * + * @group time-sensitive + */ +class ExceptionListenerTest extends TestCase +{ + public function testConstruct() + { + $logger = new TestLogger(); + $l = new ExceptionListener('foo', $logger); + + $_logger = new \ReflectionProperty(get_class($l), 'logger'); + $_logger->setAccessible(true); + $_controller = new \ReflectionProperty(get_class($l), 'controller'); + $_controller->setAccessible(true); + + $this->assertSame($logger, $_logger->getValue($l)); + $this->assertSame('foo', $_controller->getValue($l)); + } + + /** + * @dataProvider provider + */ + public function testHandleWithoutLogger($event, $event2) + { + $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); + + $l = new ExceptionListener('foo'); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo'), $event->getResponse()); + + try { + $l->onKernelException($event2); + $this->fail('RuntimeException expected'); + } catch (\RuntimeException $e) { + $this->assertSame('bar', $e->getMessage()); + $this->assertSame('foo', $e->getPrevious()->getMessage()); + } + } + + /** + * @dataProvider provider + */ + public function testHandleWithLogger($event, $event2) + { + $logger = new TestLogger(); + + $l = new ExceptionListener('foo', $logger); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo'), $event->getResponse()); + + try { + $l->onKernelException($event2); + $this->fail('RuntimeException expected'); + } catch (\RuntimeException $e) { + $this->assertSame('bar', $e->getMessage()); + $this->assertSame('foo', $e->getPrevious()->getMessage()); + } + + $this->assertEquals(3, $logger->countErrors()); + $this->assertCount(3, $logger->getLogs('critical')); + } + + public function provider() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + return array(array(null, null)); + } + + $request = new Request(); + $exception = new \Exception('foo'); + $event = new GetResponseForExceptionEvent(new TestKernel(), $request, 'foo', $exception); + $event2 = new GetResponseForExceptionEvent(new TestKernelThatThrowsException(), $request, 'foo', $exception); + + return array( + array($event, $event2), + ); + } + + public function testSubRequestFormat() + { + $listener = new ExceptionListener('foo', $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock()); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) { + return new Response($request->getRequestFormat()); + })); + + $request = Request::create('/'); + $request->setRequestFormat('xml'); + + $event = new GetResponseForExceptionEvent($kernel, $request, 'foo', new \Exception('foo')); + $listener->onKernelException($event); + + $response = $event->getResponse(); + $this->assertEquals('xml', $response->getContent()); + } +} + +class TestLogger extends Logger implements DebugLoggerInterface +{ + public function countErrors() + { + return count($this->logs['critical']); + } +} + +class TestKernel implements HttpKernelInterface +{ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + return new Response('foo'); + } +} + +class TestKernelThatThrowsException implements HttpKernelInterface +{ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + throw new \RuntimeException('bar'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.php new file mode 100644 index 00000000..464b2ab4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.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\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\UriSigner; + +class FragmentListenerTest extends TestCase +{ + public function testOnlyTriggeredOnFragmentRoute() + { + $request = Request::create('http://example.com/foo?_path=foo%3Dbar%26_controller%3Dfoo'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $expected = $request->attributes->all(); + + $listener->onKernelRequest($event); + + $this->assertEquals($expected, $request->attributes->all()); + $this->assertTrue($request->query->has('_path')); + } + + public function testOnlyTriggeredIfControllerWasNotDefinedYet() + { + $request = Request::create('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'); + $request->attributes->set('_controller', 'bar'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request, HttpKernelInterface::SUB_REQUEST); + + $expected = $request->attributes->all(); + + $listener->onKernelRequest($event); + + $this->assertEquals($expected, $request->attributes->all()); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithNonSafeMethods() + { + $request = Request::create('http://example.com/_fragment', 'POST'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithWrongSignature() + { + $request = Request::create('http://example.com/_fragment', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + public function testWithSignature() + { + $signer = new UriSigner('foo'); + $request = Request::create($signer->sign('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'), 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener($signer); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + + $this->assertEquals(array('foo' => 'bar', '_controller' => 'foo'), $request->attributes->get('_route_params')); + $this->assertFalse($request->query->has('_path')); + } + + public function testRemovesPathWithControllerDefined() + { + $request = Request::create('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request, HttpKernelInterface::SUB_REQUEST); + + $listener->onKernelRequest($event); + + $this->assertFalse($request->query->has('_path')); + } + + public function testRemovesPathWithControllerNotDefined() + { + $signer = new UriSigner('foo'); + $request = Request::create($signer->sign('http://example.com/_fragment?_path=foo%3Dbar'), 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener($signer); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + + $this->assertFalse($request->query->has('_path')); + } + + private function createGetResponseEvent(Request $request, $requestType = HttpKernelInterface::MASTER_REQUEST) + { + return new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, $requestType); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php new file mode 100644 index 00000000..2ce32819 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.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\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\LocaleListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; + +class LocaleListenerTest extends TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->disableOriginalConstructor()->getMock(); + } + + public function testDefaultLocaleWithoutSession() + { + $listener = new LocaleListener($this->requestStack, 'fr'); + $event = $this->getEvent($request = Request::create('/')); + + $listener->onKernelRequest($event); + $this->assertEquals('fr', $request->getLocale()); + } + + public function testLocaleFromRequestAttribute() + { + $request = Request::create('/'); + session_name('foo'); + $request->cookies->set('foo', 'value'); + + $request->attributes->set('_locale', 'es'); + $listener = new LocaleListener($this->requestStack, 'fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('es', $request->getLocale()); + } + + public function testLocaleSetForRoutingContext() + { + // the request context is updated + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext')->getMock(); + $context->expects($this->once())->method('setParameter')->with('_locale', 'es'); + + $router = $this->getMockBuilder('Symfony\Component\Routing\Router')->setMethods(array('getContext'))->disableOriginalConstructor()->getMock(); + $router->expects($this->once())->method('getContext')->will($this->returnValue($context)); + + $request = Request::create('/'); + + $request->attributes->set('_locale', 'es'); + $listener = new LocaleListener($this->requestStack, 'fr', $router); + $listener->onKernelRequest($this->getEvent($request)); + } + + public function testRouterResetWithParentRequestOnKernelFinishRequest() + { + // the request context is updated + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext')->getMock(); + $context->expects($this->once())->method('setParameter')->with('_locale', 'es'); + + $router = $this->getMockBuilder('Symfony\Component\Routing\Router')->setMethods(array('getContext'))->disableOriginalConstructor()->getMock(); + $router->expects($this->once())->method('getContext')->will($this->returnValue($context)); + + $parentRequest = Request::create('/'); + $parentRequest->setLocale('es'); + + $this->requestStack->expects($this->once())->method('getParentRequest')->will($this->returnValue($parentRequest)); + + $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\FinishRequestEvent')->disableOriginalConstructor()->getMock(); + + $listener = new LocaleListener($this->requestStack, 'fr', $router); + $listener->onKernelFinishRequest($event); + } + + public function testRequestLocaleIsNotOverridden() + { + $request = Request::create('/'); + $request->setLocale('de'); + $listener = new LocaleListener($this->requestStack, 'fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('de', $request->getLocale()); + } + + private function getEvent(Request $request) + { + return new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, HttpKernelInterface::MASTER_REQUEST); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php new file mode 100644 index 00000000..751aee86 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\EventListener\ProfilerListener; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Kernel; + +class ProfilerListenerTest extends TestCase +{ + /** + * Test a master and sub request with an exception and `onlyException` profiler option enabled. + */ + public function testKernelTerminate() + { + $profile = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profile') + ->disableOriginalConstructor() + ->getMock(); + + $profiler = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock(); + + $profiler->expects($this->once()) + ->method('collect') + ->will($this->returnValue($profile)); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + + $masterRequest = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $subRequest = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request') + ->disableOriginalConstructor() + ->getMock(); + + $response = $this->getMockBuilder('Symfony\Component\HttpFoundation\Response') + ->disableOriginalConstructor() + ->getMock(); + + $requestStack = new RequestStack(); + $requestStack->push($masterRequest); + + $onlyException = true; + $listener = new ProfilerListener($profiler, $requestStack, null, $onlyException); + + // master request + $listener->onKernelResponse(new FilterResponseEvent($kernel, $masterRequest, Kernel::MASTER_REQUEST, $response)); + + // sub request + $listener->onKernelException(new GetResponseForExceptionEvent($kernel, $subRequest, Kernel::SUB_REQUEST, new HttpException(404))); + $listener->onKernelResponse(new FilterResponseEvent($kernel, $subRequest, Kernel::SUB_REQUEST, $response)); + + $listener->onKernelTerminate(new PostResponseEvent($kernel, $masterRequest, $response)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php new file mode 100644 index 00000000..12a31eb3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\EventListener\ResponseListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class ResponseListenerTest extends TestCase +{ + private $dispatcher; + + private $kernel; + + protected function setUp() + { + $this->dispatcher = new EventDispatcher(); + $listener = new ResponseListener('UTF-8'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + + $this->kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->kernel = null; + } + + public function testFilterDoesNothingForSubRequests() + { + $response = new Response('foo'); + + $event = new FilterResponseEvent($this->kernel, new Request(), HttpKernelInterface::SUB_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('content-type')); + } + + public function testFilterSetsNonDefaultCharsetIfNotOverridden() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + + $event = new FilterResponseEvent($this->kernel, Request::create('/'), HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-15', $response->getCharset()); + } + + public function testFilterDoesNothingIfCharsetIsOverridden() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + $response->setCharset('ISO-8859-1'); + + $event = new FilterResponseEvent($this->kernel, Request::create('/'), HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-1', $response->getCharset()); + } + + public function testFiltersSetsNonDefaultCharsetIfNotOverriddenOnNonTextContentType() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + $request = Request::create('/'); + $request->setRequestFormat('application/json'); + + $event = new FilterResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-15', $response->getCharset()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php new file mode 100644 index 00000000..a40e5799 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\EventListener\ExceptionListener; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\Routing\RequestContext; + +class RouterListenerTest extends TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->disableOriginalConstructor()->getMock(); + } + + /** + * @dataProvider getPortData + */ + public function testPort($defaultHttpPort, $defaultHttpsPort, $uri, $expectedHttpPort, $expectedHttpsPort) + { + $urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface') + ->disableOriginalConstructor() + ->getMock(); + $context = new RequestContext(); + $context->setHttpPort($defaultHttpPort); + $context->setHttpsPort($defaultHttpsPort); + $urlMatcher->expects($this->any()) + ->method('getContext') + ->will($this->returnValue($context)); + + $listener = new RouterListener($urlMatcher, $this->requestStack); + $event = $this->createGetResponseEventForUri($uri); + $listener->onKernelRequest($event); + + $this->assertEquals($expectedHttpPort, $context->getHttpPort()); + $this->assertEquals($expectedHttpsPort, $context->getHttpsPort()); + $this->assertEquals(0 === strpos($uri, 'https') ? 'https' : 'http', $context->getScheme()); + } + + public function getPortData() + { + return array( + array(80, 443, 'http://localhost/', 80, 443), + array(80, 443, 'http://localhost:90/', 90, 443), + array(80, 443, 'https://localhost/', 80, 443), + array(80, 443, 'https://localhost:90/', 80, 90), + ); + } + + /** + * @param string $uri + * + * @return GetResponseEvent + */ + private function createGetResponseEventForUri($uri) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create($uri); + $request->attributes->set('_controller', null); // Prevents going in to routing process + + return new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidMatcher() + { + new RouterListener(new \stdClass(), $this->requestStack); + } + + public function testRequestMatcher() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->once()) + ->method('matchRequest') + ->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request')) + ->will($this->returnValue(array())); + + $listener = new RouterListener($requestMatcher, $this->requestStack, new RequestContext()); + $listener->onKernelRequest($event); + } + + public function testSubRequestWithDifferentMethod() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/', 'post'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->any()) + ->method('matchRequest') + ->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request')) + ->will($this->returnValue(array())); + + $context = new RequestContext(); + + $listener = new RouterListener($requestMatcher, $this->requestStack, new RequestContext()); + $listener->onKernelRequest($event); + + // sub-request with another HTTP method + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/', 'get'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST); + + $listener->onKernelRequest($event); + + $this->assertEquals('GET', $context->getMethod()); + } + + /** + * @dataProvider getLoggingParameterData + */ + public function testLoggingParameter($parameter, $log, $parameters) + { + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->once()) + ->method('matchRequest') + ->will($this->returnValue($parameter)); + + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger->expects($this->once()) + ->method('info') + ->with($this->equalTo($log), $this->equalTo($parameters)); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $request = Request::create('http://localhost/'); + + $listener = new RouterListener($requestMatcher, $this->requestStack, new RequestContext(), $logger); + $listener->onKernelRequest(new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST)); + } + + public function getLoggingParameterData() + { + return array( + array(array('_route' => 'foo'), 'Matched route "{route}".', array('route' => 'foo', 'route_parameters' => array('_route' => 'foo'), 'request_uri' => 'http://localhost/', 'method' => 'GET')), + array(array(), 'Matched route "{route}".', array('route' => 'n/a', 'route_parameters' => array(), 'request_uri' => 'http://localhost/', 'method' => 'GET')), + ); + } + + public function testWithBadRequest() + { + $requestStack = new RequestStack(); + + $requestMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $requestMatcher->expects($this->never())->method('matchRequest'); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new ValidateRequestListener()); + $dispatcher->addSubscriber(new RouterListener($requestMatcher, $requestStack, new RequestContext())); + $dispatcher->addSubscriber(new ExceptionListener(function () { + return new Response('Exception handled', 400); + })); + + $kernel = new HttpKernel($dispatcher, new ControllerResolver(), $requestStack, new ArgumentResolver()); + + $request = Request::create('http://localhost/'); + $request->headers->set('host', '###'); + $response = $kernel->handle($request); + $this->assertSame(400, $response->getStatusCode()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php new file mode 100644 index 00000000..1e79ebe0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.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\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class SurrogateListenerTest extends TestCase +{ + public function testFilterDoesNothingForSubRequests() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $response = new Response('foo '); + $listener = new SurrogateListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::SUB_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('Surrogate-Control')); + } + + public function testFilterWhenThereIsSomeEsiIncludes() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $response = new Response('foo '); + $listener = new SurrogateListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('content="ESI/1.0"', $event->getResponse()->headers->get('Surrogate-Control')); + } + + public function testFilterWhenThereIsNoEsiIncludes() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $response = new Response('foo'); + $listener = new SurrogateListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('Surrogate-Control')); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php new file mode 100644 index 00000000..8ada8e7d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\EventListener\SessionListener; +use Symfony\Component\HttpKernel\EventListener\TestSessionListener; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * SessionListenerTest. + * + * Tests SessionListener. + * + * @author Bulat Shakirzyanov + */ +class TestSessionListenerTest extends TestCase +{ + /** + * @var TestSessionListener + */ + private $listener; + + /** + * @var SessionInterface + */ + private $session; + + protected function setUp() + { + $this->listener = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\EventListener\AbstractTestSessionListener'); + $this->session = $this->getSession(); + } + + public function testShouldSaveMasterRequestSession() + { + $this->sessionHasBeenStarted(); + $this->sessionMustBeSaved(); + + $this->filterResponse(new Request()); + } + + public function testShouldNotSaveSubRequestSession() + { + $this->sessionMustNotBeSaved(); + + $this->filterResponse(new Request(), HttpKernelInterface::SUB_REQUEST); + } + + public function testDoesNotDeleteCookieIfUsingSessionLifetime() + { + $this->sessionHasBeenStarted(); + + $params = session_get_cookie_params(); + session_set_cookie_params(0, $params['path'], $params['domain'], $params['secure'], $params['httponly']); + + $response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST); + $cookies = $response->headers->getCookies(); + + $this->assertEquals(0, reset($cookies)->getExpiresTime()); + } + + public function testUnstartedSessionIsNotSave() + { + $this->sessionHasNotBeenStarted(); + $this->sessionMustNotBeSaved(); + + $this->filterResponse(new Request()); + } + + public function testDoesNotImplementServiceSubscriberInterface() + { + $this->assertTrue(interface_exists(ServiceSubscriberInterface::class)); + $this->assertTrue(class_exists(SessionListener::class)); + $this->assertTrue(class_exists(TestSessionListener::class)); + $this->assertFalse(is_subclass_of(SessionListener::class, ServiceSubscriberInterface::class), 'Implementing ServiceSubscriberInterface would create a dep on the DI component, which eg Silex cannot afford'); + $this->assertFalse(is_subclass_of(TestSessionListener::class, ServiceSubscriberInterface::class, 'Implementing ServiceSubscriberInterface would create a dep on the DI component, which eg Silex cannot afford')); + } + + private function filterResponse(Request $request, $type = HttpKernelInterface::MASTER_REQUEST) + { + $request->setSession($this->session); + $response = new Response(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $event = new FilterResponseEvent($kernel, $request, $type, $response); + + $this->listener->onKernelResponse($event); + + $this->assertSame($response, $event->getResponse()); + + return $response; + } + + private function sessionMustNotBeSaved() + { + $this->session->expects($this->never()) + ->method('save'); + } + + private function sessionMustBeSaved() + { + $this->session->expects($this->once()) + ->method('save'); + } + + private function sessionHasBeenStarted() + { + $this->session->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)); + } + + private function sessionHasNotBeenStarted() + { + $this->session->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(false)); + } + + private function getSession() + { + $mock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session') + ->disableOriginalConstructor() + ->getMock(); + + // set return value for getName() + $mock->expects($this->any())->method('getName')->will($this->returnValue('MOCKSESSID')); + + return $mock; + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php new file mode 100644 index 00000000..23b83317 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\EventListener\TranslatorListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class TranslatorListenerTest extends TestCase +{ + private $listener; + private $translator; + private $requestStack; + + protected function setUp() + { + $this->translator = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock(); + $this->requestStack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); + $this->listener = new TranslatorListener($this->translator, $this->requestStack); + } + + public function testLocaleIsSetInOnKernelRequest() + { + $this->translator + ->expects($this->once()) + ->method('setLocale') + ->with($this->equalTo('fr')); + + $event = new GetResponseEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST); + $this->listener->onKernelRequest($event); + } + + public function testDefaultLocaleIsUsedOnExceptionsInOnKernelRequest() + { + $this->translator + ->expects($this->at(0)) + ->method('setLocale') + ->will($this->throwException(new \InvalidArgumentException())); + $this->translator + ->expects($this->at(1)) + ->method('setLocale') + ->with($this->equalTo('en')); + + $event = new GetResponseEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST); + $this->listener->onKernelRequest($event); + } + + public function testLocaleIsSetInOnKernelFinishRequestWhenParentRequestExists() + { + $this->translator + ->expects($this->once()) + ->method('setLocale') + ->with($this->equalTo('fr')); + + $this->setMasterRequest($this->createRequest('fr')); + $event = new FinishRequestEvent($this->createHttpKernel(), $this->createRequest('de'), HttpKernelInterface::SUB_REQUEST); + $this->listener->onKernelFinishRequest($event); + } + + public function testLocaleIsNotSetInOnKernelFinishRequestWhenParentRequestDoesNotExist() + { + $this->translator + ->expects($this->never()) + ->method('setLocale'); + + $event = new FinishRequestEvent($this->createHttpKernel(), $this->createRequest('de'), HttpKernelInterface::SUB_REQUEST); + $this->listener->onKernelFinishRequest($event); + } + + public function testDefaultLocaleIsUsedOnExceptionsInOnKernelFinishRequest() + { + $this->translator + ->expects($this->at(0)) + ->method('setLocale') + ->will($this->throwException(new \InvalidArgumentException())); + $this->translator + ->expects($this->at(1)) + ->method('setLocale') + ->with($this->equalTo('en')); + + $this->setMasterRequest($this->createRequest('fr')); + $event = new FinishRequestEvent($this->createHttpKernel(), $this->createRequest('de'), HttpKernelInterface::SUB_REQUEST); + $this->listener->onKernelFinishRequest($event); + } + + private function createHttpKernel() + { + return $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + } + + private function createRequest($locale) + { + $request = new Request(); + $request->setLocale($locale); + + return $request; + } + + private function setMasterRequest($request) + { + $this->requestStack + ->expects($this->any()) + ->method('getParentRequest') + ->will($this->returnValue($request)); + } +} diff --git a/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.php new file mode 100644 index 00000000..d0609432 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.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\HttpKernel\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +class ValidateRequestListenerTest extends TestCase +{ + /** + * @expectedException \Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException + */ + public function testListenerThrowsWhenMasterRequestHasInconsistentClientIps() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + + $request = new Request(); + $request->setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_FOR | Request::HEADER_FORWARDED); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('FORWARDED', 'for=2.2.2.2'); + $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); + + $dispatcher->addListener(KernelEvents::REQUEST, array(new ValidateRequestListener(), 'onKernelRequest')); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $dispatcher->dispatch(KernelEvents::REQUEST, $event); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php new file mode 100644 index 00000000..2bfcb2bf --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/AccessDeniedHttpExceptionTest.php @@ -0,0 +1,13 @@ + 'Test')), + array(array('X-Test' => 1)), + array( + array( + array('X-Test' => 'Test'), + array('X-Test-2' => 'Test-2'), + ), + ), + ); + } + + public function testHeadersDefault() + { + $exception = $this->createException(); + $this->assertSame(array(), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersConstructor($headers) + { + $exception = new HttpException(200, null, null, $headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = $this->createException(); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new HttpException(200); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php new file mode 100644 index 00000000..462d3ca4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/LengthRequiredHttpExceptionTest.php @@ -0,0 +1,13 @@ +assertSame(array('Allow' => 'GET, PUT'), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new MethodNotAllowedHttpException(array('GET')); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php new file mode 100644 index 00000000..4c0db7a3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/NotAcceptableHttpExceptionTest.php @@ -0,0 +1,13 @@ +assertSame(array('Retry-After' => 10), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new ServiceUnavailableHttpException(10); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new ServiceUnavailableHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php new file mode 100644 index 00000000..2079bb33 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/TooManyRequestsHttpExceptionTest.php @@ -0,0 +1,29 @@ +assertSame(array('Retry-After' => 10), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new TooManyRequestsHttpException(10); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new TooManyRequestsHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php new file mode 100644 index 00000000..37a0028d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/UnauthorizedHttpExceptionTest.php @@ -0,0 +1,24 @@ +assertSame(array('WWW-Authenticate' => 'Challenge'), $exception->getHeaders()); + } + + /** + * @dataProvider headerDataProvider + */ + public function testHeadersSetter($headers) + { + $exception = new UnauthorizedHttpException('Challenge'); + $exception->setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php new file mode 100644 index 00000000..760366c6 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/UnprocessableEntityHttpExceptionTest.php @@ -0,0 +1,28 @@ +setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException() + { + return new UnprocessableEntityHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php b/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php new file mode 100644 index 00000000..d47287a1 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Exception/UnsupportedMediaTypeHttpExceptionTest.php @@ -0,0 +1,23 @@ +setHeaders($headers); + $this->assertSame($headers, $exception->getHeaders()); + } + + protected function createException($headers = array()) + { + return new UnsupportedMediaTypeHttpException(); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php b/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.php new file mode 100644 index 00000000..b6cf1cba --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/123/Kernel123.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\HttpKernel\Tests\Fixtures\_123; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class Kernel123 extends Kernel +{ + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function getCacheDir() + { + return sys_get_temp_dir().'/'.Kernel::VERSION.'/kernel123/cache/'.$this->environment; + } + + public function getLogDir() + { + return sys_get_temp_dir().'/'.Kernel::VERSION.'/kernel123/logs'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/hide.txt b/vendor/symfony/http-kernel/Tests/Fixtures/BaseBundle/Resources/hide.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/bar.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/bar.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle1Bundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Bundle2Bundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt b/vendor/symfony/http-kernel/Tests/Fixtures/ChildBundle/Resources/hide.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php new file mode 100644 index 00000000..e8e0b603 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/BasicTypesController.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +class BasicTypesController +{ + public function action(string $foo, int $bar, float $baz) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php new file mode 100644 index 00000000..9b4754b4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingRequest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +use Symfony\Component\HttpFoundation\Request; + +class ExtendingRequest extends Request +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php new file mode 100644 index 00000000..9fa54ee8 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/ExtendingSession.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +use Symfony\Component\HttpFoundation\Session\Session; + +class ExtendingSession extends Session +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php new file mode 100644 index 00000000..9db4df7b --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/NullableController.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +class NullableController +{ + public function action(?string $foo, ?\stdClass $bar, ?string $baz = 'value', $mandatory) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php new file mode 100644 index 00000000..c3981245 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\Controller; + +class VariadicController +{ + public function action($foo, ...$bar) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php b/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php new file mode 100644 index 00000000..867ccdce --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/DataCollector/CloneVarDataCollector.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; + +class CloneVarDataCollector extends DataCollector +{ + private $varToClone; + + public function __construct($varToClone) + { + $this->varToClone = $varToClone; + } + + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = $this->cloneVar($this->varToClone); + } + + public function getData() + { + return $this->data; + } + + public function getName() + { + return 'clone_var'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php new file mode 100644 index 00000000..c8bfd36e --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionAbsentBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionAbsentBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php new file mode 100644 index 00000000..b43bc665 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.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\HttpKernel\Tests\Fixtures\ExtensionLoadedBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; + +class ExtensionLoadedExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php new file mode 100644 index 00000000..3af81cb0 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionLoadedBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionLoadedBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php new file mode 100644 index 00000000..0fd64316 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/DependencyInjection/ExtensionNotValidExtension.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionNotValidBundle\DependencyInjection; + +class ExtensionNotValidExtension +{ + public function getAlias() + { + return 'extension_not_valid'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php new file mode 100644 index 00000000..34e29203 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionNotValidBundle/ExtensionNotValidBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionNotValidBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionNotValidBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php new file mode 100644 index 00000000..977976b7 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command; + +use Symfony\Component\Console\Command\Command; + +class FooCommand extends Command +{ + protected function configure() + { + $this->setName('foo'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php new file mode 100644 index 00000000..10857171 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.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\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; + +class ExtensionPresentExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php new file mode 100644 index 00000000..36a7ad40 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionPresentBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.php new file mode 100644 index 00000000..a1102ab7 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForOverrideName.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\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class KernelForOverrideName extends Kernel +{ + protected $name = 'overridden'; + + public function registerBundles() + { + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.php new file mode 100644 index 00000000..5fd61bbc --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/KernelForTest.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\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class KernelForTest extends Kernel +{ + public function getBundleMap() + { + return $this->bundleMap; + } + + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function isBooted() + { + return $this->booted; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php b/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.php new file mode 100644 index 00000000..cee1b09f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/KernelWithoutBundles.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\HttpKernel\Tests\Fixtures; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Kernel; + +class KernelWithoutBundles extends Kernel +{ + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + protected function build(ContainerBuilder $container) + { + $container->setParameter('test_executed', true); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/BaseBundle/hide.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/ChildBundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/Resources/FooBundle/foo.txt b/vendor/symfony/http-kernel/Tests/Fixtures/Resources/FooBundle/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php b/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php new file mode 100644 index 00000000..e7d60cff --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/TestClient.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Client; + +class TestClient extends Client +{ + protected function getScript($request) + { + $script = parent::getScript($request); + + $autoload = file_exists(__DIR__.'/../../vendor/autoload.php') + ? __DIR__.'/../../vendor/autoload.php' + : __DIR__.'/../../../../../../vendor/autoload.php' + ; + + $script = preg_replace('/(\->register\(\);)/', "$0\nrequire_once '$autoload';\n", $script); + + return $script; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php b/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.php new file mode 100644 index 00000000..da7ef5bd --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/TestEventDispatcher.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\HttpKernel\Tests\Fixtures; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestEventDispatcher extends EventDispatcher implements TraceableEventDispatcherInterface +{ + public function getCalledListeners() + { + return array('foo'); + } + + public function getNotCalledListeners() + { + return array('bar'); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php new file mode 100644 index 00000000..bfe922e2 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\UriSigner; + +class EsiFragmentRendererTest extends TestCase +{ + public function testRenderFallbackToInlineStrategyIfEsiNotSupported() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + /** + * @group legacy + * @expectedDeprecation Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is deprecated %s. + */ + public function testRenderFallbackWithObjectAttributesIsDeprecated() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true), new UriSigner('foo')); + $request = Request::create('/'); + $reference = new ControllerReference('main_controller', array('foo' => array('a' => array(), 'b' => new \stdClass())), array()); + $strategy->render($reference, $request); + } + + public function testRender() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $this->assertEquals('', $strategy->render('/', $request)->getContent()); + $this->assertEquals("\n", $strategy->render('/', $request, array('comment' => 'This is a comment'))->getContent()); + $this->assertEquals('', $strategy->render('/', $request, array('alt' => 'foo'))->getContent()); + } + + public function testRenderControllerReference() + { + $signer = new UriSigner('foo'); + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(), $signer); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $reference = new ControllerReference('main_controller', array(), array()); + $altReference = new ControllerReference('alt_controller', array(), array()); + + $this->assertEquals( + '', + $strategy->render($reference, $request, array('alt' => $altReference))->getContent() + ); + } + + /** + * @expectedException \LogicException + */ + public function testRenderControllerReferenceWithoutSignerThrowsException() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $strategy->render(new ControllerReference('main_controller'), $request); + } + + /** + * @expectedException \LogicException + */ + public function testRenderAltControllerReferenceWithoutSignerThrowsException() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $strategy->render('/', $request, array('alt' => new ControllerReference('alt_controller'))); + } + + private function getInlineStrategy($called = false) + { + $inline = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer')->disableOriginalConstructor()->getMock(); + + if ($called) { + $inline->expects($this->once())->method('render'); + } + + return $inline; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php b/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.php new file mode 100644 index 00000000..9c906b50 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.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\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class FragmentHandlerTest extends TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMockBuilder('Symfony\\Component\\HttpFoundation\\RequestStack') + ->disableOriginalConstructor() + ->getMock() + ; + $this->requestStack + ->expects($this->any()) + ->method('getCurrentRequest') + ->will($this->returnValue(Request::create('/'))) + ; + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRenderWhenRendererDoesNotExist() + { + $handler = new FragmentHandler($this->requestStack); + $handler->render('/', 'foo'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRenderWithUnknownRenderer() + { + $handler = $this->getHandler($this->returnValue(new Response('foo'))); + + $handler->render('/', 'bar'); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Error when rendering "http://localhost/" (Status code is 404). + */ + public function testDeliverWithUnsuccessfulResponse() + { + $handler = $this->getHandler($this->returnValue(new Response('foo', 404))); + + $handler->render('/', 'foo'); + } + + public function testRender() + { + $handler = $this->getHandler($this->returnValue(new Response('foo')), array('/', Request::create('/'), array('foo' => 'foo', 'ignore_errors' => true))); + + $this->assertEquals('foo', $handler->render('/', 'foo', array('foo' => 'foo'))); + } + + protected function getHandler($returnValue, $arguments = array()) + { + $renderer = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface')->getMock(); + $renderer + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo')) + ; + $e = $renderer + ->expects($this->any()) + ->method('render') + ->will($returnValue) + ; + + if ($arguments) { + call_user_func_array(array($e, 'with'), $arguments); + } + + $handler = new FragmentHandler($this->requestStack); + $handler->addRenderer($renderer); + + return $handler; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php new file mode 100644 index 00000000..1be052e5 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\HttpFoundation\Request; + +class HIncludeFragmentRendererTest extends TestCase +{ + /** + * @expectedException \LogicException + */ + public function testRenderExceptionWhenControllerAndNoSigner() + { + $strategy = new HIncludeFragmentRenderer(); + $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/')); + } + + public function testRenderWithControllerAndSigner() + { + $strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo')); + + $this->assertEquals('', $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/'))->getContent()); + } + + public function testRenderWithUri() + { + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('', $strategy->render('/foo', Request::create('/'))->getContent()); + + $strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo')); + $this->assertEquals('', $strategy->render('/foo', Request::create('/'))->getContent()); + } + + public function testRenderWithDefault() + { + // only default + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + + // only global default + $strategy = new HIncludeFragmentRenderer(null, null, 'global_default'); + $this->assertEquals('global_default', $strategy->render('/foo', Request::create('/'), array())->getContent()); + + // global default and default + $strategy = new HIncludeFragmentRenderer(null, null, 'global_default'); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + } + + public function testRenderWithAttributesOptions() + { + // with id + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'id' => 'bar'))->getContent()); + + // with attributes + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'attributes' => array('p1' => 'v1', 'p2' => 'v2')))->getContent()); + + // with id & attributes + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'id' => 'bar', 'attributes' => array('p1' => 'v1', 'p2' => 'v2')))->getContent()); + } + + public function testRenderWithDefaultText() + { + $engine = $this->getMockBuilder('Symfony\\Component\\Templating\\EngineInterface')->getMock(); + $engine->expects($this->once()) + ->method('exists') + ->with('default') + ->will($this->throwException(new \InvalidArgumentException())); + + // only default + $strategy = new HIncludeFragmentRenderer($engine); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php new file mode 100644 index 00000000..18e55a5b --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class InlineFragmentRendererTest extends TestCase +{ + public function testRender() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->returnValue(new Response('foo')))); + + $this->assertEquals('foo', $strategy->render('/', Request::create('/'))->getContent()); + } + + public function testRenderWithControllerReference() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->returnValue(new Response('foo')))); + + $this->assertEquals('foo', $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/'))->getContent()); + } + + public function testRenderWithObjectsAsAttributes() + { + $object = new \stdClass(); + + $subRequest = Request::create('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dmain_controller'); + $subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en')); + $subRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($subRequest)); + + $this->assertSame('foo', $strategy->render(new ControllerReference('main_controller', array('object' => $object), array()), Request::create('/'))->getContent()); + } + + /** + * @group legacy + */ + public function testRenderWithObjectsAsAttributesPassedAsObjectsInTheControllerLegacy() + { + $resolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver')->setMethods(array('getController'))->getMock(); + $resolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function (\stdClass $object, Bar $object1) { + return new Response($object1->getBar()); + })) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $resolver, new RequestStack()); + $renderer = new InlineFragmentRenderer($kernel); + + $response = $renderer->render(new ControllerReference('main_controller', array('object' => new \stdClass(), 'object1' => new Bar()), array()), Request::create('/')); + $this->assertEquals('bar', $response->getContent()); + } + + /** + * @group legacy + */ + public function testRenderWithObjectsAsAttributesPassedAsObjectsInTheController() + { + $resolver = $this->getMockBuilder(ControllerResolverInterface::class)->getMock(); + $resolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function (\stdClass $object, Bar $object1) { + return new Response($object1->getBar()); + })) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $resolver, new RequestStack(), new ArgumentResolver()); + $renderer = new InlineFragmentRenderer($kernel); + + $response = $renderer->render(new ControllerReference('main_controller', array('object' => new \stdClass(), 'object1' => new Bar()), array()), Request::create('/')); + $this->assertEquals('bar', $response->getContent()); + } + + public function testRenderWithTrustedHeaderDisabled() + { + Request::setTrustedProxies(array(), 0); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/'))); + $this->assertSame('foo', $strategy->render('/', Request::create('/'))->getContent()); + + Request::setTrustedProxies(array(), -1); + } + + /** + * @expectedException \RuntimeException + */ + public function testRenderExceptionNoIgnoreErrors() + { + $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $dispatcher->expects($this->never())->method('dispatch'); + + $strategy = new InlineFragmentRenderer($this->getKernel($this->throwException(new \RuntimeException('foo'))), $dispatcher); + + $this->assertEquals('foo', $strategy->render('/', Request::create('/'))->getContent()); + } + + public function testRenderExceptionIgnoreErrors() + { + $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $dispatcher->expects($this->once())->method('dispatch')->with(KernelEvents::EXCEPTION); + + $strategy = new InlineFragmentRenderer($this->getKernel($this->throwException(new \RuntimeException('foo'))), $dispatcher); + + $this->assertEmpty($strategy->render('/', Request::create('/'), array('ignore_errors' => true))->getContent()); + } + + public function testRenderExceptionIgnoreErrorsWithAlt() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->onConsecutiveCalls( + $this->throwException(new \RuntimeException('foo')), + $this->returnValue(new Response('bar')) + ))); + + $this->assertEquals('bar', $strategy->render('/', Request::create('/'), array('ignore_errors' => true, 'alt' => '/foo'))->getContent()); + } + + private function getKernel($returnValue) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel + ->expects($this->any()) + ->method('handle') + ->will($returnValue) + ; + + return $kernel; + } + + public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() + { + $controllerResolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface')->getMock(); + $controllerResolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function () { + ob_start(); + echo 'bar'; + throw new \RuntimeException(); + })) + ; + + $argumentResolver = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface')->getMock(); + $argumentResolver + ->expects($this->once()) + ->method('getArguments') + ->will($this->returnValue(array())) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $controllerResolver, new RequestStack(), $argumentResolver); + $renderer = new InlineFragmentRenderer($kernel); + + // simulate a main request with output buffering + ob_start(); + echo 'Foo'; + + // simulate a sub-request with output buffering and an exception + $renderer->render('/', Request::create('/'), array('ignore_errors' => true)); + + $this->assertEquals('Foo', ob_get_clean()); + } + + public function testESIHeaderIsKeptInSubrequest() + { + $expectedSubRequest = Request::create('/'); + $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + + if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + } + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $strategy->render('/', $request); + } + + public function testESIHeaderIsKeptInSubrequestWithTrustedHeaderDisabled() + { + Request::setTrustedProxies(array(), 0); + + $this->testESIHeaderIsKeptInSubrequest(); + + Request::setTrustedProxies(array(), -1); + } + + public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest() + { + $expectedSubRequest = Request::create('/'); + if (Request::HEADER_X_FORWARDED_FOR & Request::getTrustedHeaderSet()) { + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + } + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest)); + $request = Request::create('/', 'GET', array(), array(), array(), array('HTTP_IF_MODIFIED_SINCE' => 'Fri, 01 Jan 2016 00:00:00 GMT', 'HTTP_IF_NONE_MATCH' => '*')); + $strategy->render('/', $request); + } + + /** + * Creates a Kernel expecting a request equals to $request + * Allows delta in comparison in case REQUEST_TIME changed by 1 second. + */ + private function getKernelExpectingRequest(Request $request, $strict = false) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); + $kernel + ->expects($this->once()) + ->method('handle') + ->with($this->equalTo($request, 1)) + ->willReturn(new Response('foo')); + + return $kernel; + } +} + +class Bar +{ + public $bar = 'bar'; + + public function getBar() + { + return $this->bar; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php new file mode 100644 index 00000000..3a040ded --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +class RoutableFragmentRendererTest extends TestCase +{ + /** + * @dataProvider getGenerateFragmentUriData + */ + public function testGenerateFragmentUri($uri, $controller) + { + $this->assertEquals($uri, $this->callGenerateFragmentUriMethod($controller, Request::create('/'))); + } + + /** + * @dataProvider getGenerateFragmentUriData + */ + public function testGenerateAbsoluteFragmentUri($uri, $controller) + { + $this->assertEquals('http://localhost'.$uri, $this->callGenerateFragmentUriMethod($controller, Request::create('/'), true)); + } + + public function getGenerateFragmentUriData() + { + return array( + array('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array(), array())), + array('/_fragment?_path=_format%3Dxml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('_format' => 'xml'), array())), + array('/_fragment?_path=foo%3Dfoo%26_format%3Djson%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => 'foo', '_format' => 'json'), array())), + array('/_fragment?bar=bar&_path=foo%3Dfoo%26_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => 'foo'), array('bar' => 'bar'))), + array('/_fragment?foo=foo&_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array(), array('foo' => 'foo'))), + array('/_fragment?_path=foo%255B0%255D%3Dfoo%26foo%255B1%255D%3Dbar%26_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => array('foo', 'bar')), array())), + ); + } + + public function testGenerateFragmentUriWithARequest() + { + $request = Request::create('/'); + $request->attributes->set('_format', 'json'); + $request->setLocale('fr'); + $controller = new ControllerReference('controller', array(), array()); + + $this->assertEquals('/_fragment?_path=_format%3Djson%26_locale%3Dfr%26_controller%3Dcontroller', $this->callGenerateFragmentUriMethod($controller, $request)); + } + + /** + * @expectedException \LogicException + * @dataProvider getGenerateFragmentUriDataWithNonScalar + */ + public function testGenerateFragmentUriWithNonScalar($controller) + { + $this->callGenerateFragmentUriMethod($controller, Request::create('/')); + } + + public function getGenerateFragmentUriDataWithNonScalar() + { + return array( + array(new ControllerReference('controller', array('foo' => new Foo(), 'bar' => 'bar'), array())), + array(new ControllerReference('controller', array('foo' => array('foo' => 'foo'), 'bar' => array('bar' => new Foo())), array())), + ); + } + + private function callGenerateFragmentUriMethod(ControllerReference $reference, Request $request, $absolute = false) + { + $renderer = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\Fragment\RoutableFragmentRenderer'); + $r = new \ReflectionObject($renderer); + $m = $r->getMethod('generateFragmentUri'); + $m->setAccessible(true); + + return $m->invoke($renderer, $reference, $request, $absolute); + } +} + +class Foo +{ + public $foo; + + public function getFoo() + { + return $this->foo; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php b/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.php new file mode 100644 index 00000000..b537625f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/SsiFragmentRendererTest.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\HttpKernel\Tests\Fragment; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\SsiFragmentRenderer; +use Symfony\Component\HttpKernel\HttpCache\Ssi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\UriSigner; + +class SsiFragmentRendererTest extends TestCase +{ + public function testRenderFallbackToInlineStrategyIfSsiNotSupported() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + public function testRender() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $this->assertEquals('', $strategy->render('/', $request)->getContent()); + $this->assertEquals('', $strategy->render('/', $request, array('comment' => 'This is a comment'))->getContent(), 'Strategy options should not impact the ssi include tag'); + } + + public function testRenderControllerReference() + { + $signer = new UriSigner('foo'); + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy(), $signer); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $reference = new ControllerReference('main_controller', array(), array()); + $altReference = new ControllerReference('alt_controller', array(), array()); + + $this->assertEquals( + '', + $strategy->render($reference, $request, array('alt' => $altReference))->getContent() + ); + } + + /** + * @expectedException \LogicException + */ + public function testRenderControllerReferenceWithoutSignerThrowsException() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $strategy->render(new ControllerReference('main_controller'), $request); + } + + /** + * @expectedException \LogicException + */ + public function testRenderAltControllerReferenceWithoutSignerThrowsException() + { + $strategy = new SsiFragmentRenderer(new Ssi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'SSI/1.0'); + + $strategy->render('/', $request, array('alt' => new ControllerReference('alt_controller'))); + } + + private function getInlineStrategy($called = false) + { + $inline = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer')->disableOriginalConstructor()->getMock(); + + if ($called) { + $inline->expects($this->once())->method('render'); + } + + return $inline; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php new file mode 100644 index 00000000..a8662b2a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class EsiTest extends TestCase +{ + public function testHasSurrogateEsiCapability() + { + $esi = new Esi(); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $this->assertTrue($esi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'foobar'); + $this->assertFalse($esi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $this->assertFalse($esi->hasSurrogateCapability($request)); + } + + public function testAddSurrogateEsiCapability() + { + $esi = new Esi(); + + $request = Request::create('/'); + $esi->addSurrogateCapability($request); + $this->assertEquals('symfony="ESI/1.0"', $request->headers->get('Surrogate-Capability')); + + $esi->addSurrogateCapability($request); + $this->assertEquals('symfony="ESI/1.0", symfony="ESI/1.0"', $request->headers->get('Surrogate-Capability')); + } + + public function testAddSurrogateControl() + { + $esi = new Esi(); + + $response = new Response('foo '); + $esi->addSurrogateControl($response); + $this->assertEquals('content="ESI/1.0"', $response->headers->get('Surrogate-Control')); + + $response = new Response('foo'); + $esi->addSurrogateControl($response); + $this->assertEquals('', $response->headers->get('Surrogate-Control')); + } + + public function testNeedsEsiParsing() + { + $esi = new Esi(); + + $response = new Response(); + $response->headers->set('Surrogate-Control', 'content="ESI/1.0"'); + $this->assertTrue($esi->needsParsing($response)); + + $response = new Response(); + $this->assertFalse($esi->needsParsing($response)); + } + + public function testRenderIncludeTag() + { + $esi = new Esi(); + + $this->assertEquals('', $esi->renderIncludeTag('/', '/alt', true)); + $this->assertEquals('', $esi->renderIncludeTag('/', '/alt', false)); + $this->assertEquals('', $esi->renderIncludeTag('/')); + $this->assertEquals(''."\n".'', $esi->renderIncludeTag('/', '/alt', true, 'some comment')); + } + + public function testProcessDoesNothingIfContentTypeIsNotHtml() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(); + $response->headers->set('Content-Type', 'text/plain'); + $esi->process($request, $response); + + $this->assertFalse($response->headers->has('x-body-eval')); + } + + public function testMultilineEsiRemoveTagsAreRemoved() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(' www.example.com Keep this'."\n www.example.com And this"); + $esi->process($request, $response); + + $this->assertEquals(' Keep this And this', $response->getContent()); + } + + public function testCommentTagsAreRemoved() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(' Keep this'); + $esi->process($request, $response); + + $this->assertEquals(' Keep this', $response->getContent()); + } + + public function testProcess() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent()); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'foo\\\'\', \'bar\\\'\', true) ?>'."\n", $response->getContent()); + + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + } + + public function testProcessEscapesPhpTags() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(''); + $esi->process($request, $response); + + $this->assertEquals('php cript language=php>', $response->getContent()); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessWhenNoSrcInAnEsi() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $esi->process($request, $response); + } + + public function testProcessRemoveSurrogateControlHeader() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $response->headers->set('Surrogate-Control', 'content="ESI/1.0"'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + + $response->headers->set('Surrogate-Control', 'no-store, content="ESI/1.0"'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + + $response->headers->set('Surrogate-Control', 'content="ESI/1.0", no-store'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + } + + public function testHandle() + { + $esi = new Esi(); + $cache = $this->getCache(Request::create('/'), new Response('foo')); + $this->assertEquals('foo', $esi->handle($cache, '/', '/alt', true)); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenResponseIsNot200() + { + $esi = new Esi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $esi->handle($cache, '/', '/alt', false); + } + + public function testHandleWhenResponseIsNot200AndErrorsAreIgnored() + { + $esi = new Esi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $this->assertEquals('', $esi->handle($cache, '/', '/alt', true)); + } + + public function testHandleWhenResponseIsNot200AndAltIsPresent() + { + $esi = new Esi(); + $response1 = new Response('foo'); + $response1->setStatusCode(404); + $response2 = new Response('bar'); + $cache = $this->getCache(Request::create('/'), array($response1, $response2)); + $this->assertEquals('bar', $esi->handle($cache, '/', '/alt', false)); + } + + protected function getCache($request, $response) + { + $cache = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpCache\HttpCache')->setMethods(array('getRequest', 'handle'))->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + if (is_array($response)) { + $cache->expects($this->any()) + ->method('handle') + ->will(call_user_func_array(array($this, 'onConsecutiveCalls'), $response)) + ; + } else { + $cache->expects($this->any()) + ->method('handle') + ->will($this->returnValue($response)) + ; + } + + return $cache; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php new file mode 100644 index 00000000..a24380c4 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php @@ -0,0 +1,1367 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * @group time-sensitive + */ +class HttpCacheTest extends HttpCacheTestCase +{ + public function testTerminateDelegatesTerminationOnlyForTerminableInterface() + { + $storeMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface') + ->disableOriginalConstructor() + ->getMock(); + + // does not implement TerminableInterface + $kernel = new TestKernel(); + $httpCache = new HttpCache($kernel, $storeMock); + $httpCache->terminate(Request::create('/'), new Response()); + + $this->assertFalse($kernel->terminateCalled, 'terminate() is never called if the kernel class does not implement TerminableInterface'); + + // implements TerminableInterface + $kernelMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Kernel') + ->disableOriginalConstructor() + ->setMethods(array('terminate', 'registerBundles', 'registerContainerConfiguration')) + ->getMock(); + + $kernelMock->expects($this->once()) + ->method('terminate'); + + $kernel = new HttpCache($kernelMock, $storeMock); + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testPassesOnNonGetHeadRequests() + { + $this->setNextResponse(200); + $this->request('POST', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('pass'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testInvalidatesOnPostPutDeleteRequests() + { + foreach (array('post', 'put', 'delete') as $method) { + $this->setNextResponse(200); + $this->request($method, '/'); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('invalidate'); + $this->assertTraceContains('pass'); + } + } + + public function testDoesNotCacheWithAuthorizationRequestHeaderAndNonPublicResponse() + { + $this->setNextResponse(200, array('ETag' => '"Foo"')); + $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesCacheWithAuthorizationRequestHeaderAndPublicResponse() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"Foo"')); + $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + $this->assertEquals('public', $this->response->headers->get('Cache-Control')); + } + + public function testDoesNotCacheWithCookieHeaderAndNonPublicResponse() + { + $this->setNextResponse(200, array('ETag' => '"Foo"')); + $this->request('GET', '/', array(), array('foo' => 'bar')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesNotCacheRequestsWithACookieHeader() + { + $this->setNextResponse(200); + $this->request('GET', '/', array(), array('foo' => 'bar')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testRespondsWith304WhenIfModifiedSinceMatchesLastModified() + { + $time = \DateTime::createFromFormat('U', time()); + + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822), 'Content-Type' => 'text/plain'), 'Hello World'); + $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->headers->get('Content-Type')); + $this->assertEmpty($this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testRespondsWith304WhenIfNoneMatchMatchesETag() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '12345', 'Content-Type' => 'text/plain'), 'Hello World'); + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345')); + + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->headers->get('Content-Type')); + $this->assertTrue($this->response->headers->has('ETag')); + $this->assertEmpty($this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testRespondsWith304OnlyIfIfNoneMatchAndIfModifiedSinceBothMatch() + { + $time = \DateTime::createFromFormat('U', time()); + + $this->setNextResponse(200, array(), '', function ($request, $response) use ($time) { + $response->setStatusCode(200); + $response->headers->set('ETag', '12345'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('Hello World'); + }); + + // only ETag matches + $t = \DateTime::createFromFormat('U', time() - 3600); + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $t->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + + // only Last-Modified matches + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '1234', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + + // Both matches + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + } + + public function testIncrementsMaxAgeWhenNoDateIsSpecifiedEventWhenUsingETag() + { + $this->setNextResponse( + 200, + array( + 'ETag' => '1234', + 'Cache-Control' => 'public, s-maxage=60', + ) + ); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + sleep(2); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertEquals(2, $this->response->headers->get('Age')); + } + + public function testValidatesPrivateResponsesCachedOnTheClient() + { + $this->setNextResponse(200, array(), '', function ($request, $response) { + $etags = preg_split('/\s*,\s*/', $request->headers->get('IF_NONE_MATCH')); + if ($request->cookies->has('authenticated')) { + $response->headers->set('Cache-Control', 'private, no-store'); + $response->setETag('"private tag"'); + if (in_array('"private tag"', $etags)) { + $response->setStatusCode(304); + } else { + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('private data'); + } + } else { + $response->headers->set('Cache-Control', 'public'); + $response->setETag('"public tag"'); + if (in_array('"public tag"', $etags)) { + $response->setStatusCode(304); + } else { + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('public data'); + } + } + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('"public tag"', $this->response->headers->get('ETag')); + $this->assertEquals('public data', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $this->request('GET', '/', array(), array('authenticated' => '')); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('"private tag"', $this->response->headers->get('ETag')); + $this->assertEquals('private data', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceNotContains('store'); + } + + public function testStoresResponsesWhenNoCacheRequestDirectivePresent() + { + $time = \DateTime::createFromFormat('U', time() + 5); + + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testReloadsResponsesWhenCacheHitsButNoCacheRequestDirectivePresentWhenAllowReloadIsSetTrue() + { + $count = 0; + + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) { + ++$count; + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_reload'] = true; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Goodbye World', $this->response->getContent()); + $this->assertTraceContains('reload'); + $this->assertTraceContains('store'); + } + + public function testDoesNotReloadResponsesWhenAllowReloadIsSetFalseDefault() + { + $count = 0; + + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) { + ++$count; + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_reload'] = false; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('reload'); + + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('reload'); + } + + public function testRevalidatesFreshCacheEntryWhenMaxAgeRequestDirectiveIsExceededWhenAllowRevalidateOptionIsSetTrue() + { + $count = 0; + + $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) { + ++$count; + $response->headers->set('Cache-Control', 'public, max-age=10000'); + $response->setETag($count); + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_revalidate'] = true; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Goodbye World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + } + + public function testDoesNotRevalidateFreshCacheEntryWhenEnableRevalidateOptionIsSetFalseDefault() + { + $count = 0; + + $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) { + ++$count; + $response->headers->set('Cache-Control', 'public, max-age=10000'); + $response->setETag($count); + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_revalidate'] = false; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('stale'); + $this->assertTraceNotContains('invalid'); + $this->assertTraceContains('fresh'); + + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('stale'); + $this->assertTraceNotContains('invalid'); + $this->assertTraceContains('fresh'); + } + + public function testFetchesResponseFromBackendWhenCacheMisses() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testDoesNotCacheSomeStatusCodeResponses() + { + foreach (array_merge(range(201, 202), range(204, 206), range(303, 305), range(400, 403), range(405, 409), range(411, 417), range(500, 505)) as $code) { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse($code, array('Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals($code, $this->response->getStatusCode()); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + } + + public function testDoesNotCacheResponsesWithExplicitNoStoreDirective() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'no-store')); + + $this->request('GET', '/'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesNotCacheResponsesWithoutFreshnessInformationOrAValidator() + { + $this->setNextResponse(); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceNotContains('store'); + } + + public function testCachesResponsesWithExplicitNoCacheDirective() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, no-cache')); + + $this->request('GET', '/'); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testCachesResponsesWithAnExpirationHeader() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithAMaxAgeDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=5')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithASMaxAgeDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 's-maxage=5')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithALastModifiedValidatorButNoFreshnessInformation() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testCachesResponsesWithAnETagValidatorButNoFreshnessInformation() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"123456"')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testHitsCachedResponsesWithExpiresHeader() + { + $time1 = \DateTime::createFromFormat('U', time() - 5); + $time2 = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Date' => $time1->format(DATE_RFC2822), 'Expires' => $time2->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testHitsCachedResponseWithMaxAgeDirective() + { + $time = \DateTime::createFromFormat('U', time() - 5); + $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, max-age=10')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testDegradationWhenCacheLocked() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Skips on windows to avoid permissions issues.'); + } + + $this->cacheConfig['stale_while_revalidate'] = 10; + + // The prescence of Last-Modified makes this cacheable (because Response::isValidateable() then). + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=5', 'Last-Modified' => 'some while ago'), 'Old response'); + $this->request('GET', '/'); // warm the cache + + // Now, lock the cache + $concurrentRequest = Request::create('/', 'GET'); + $this->store->lock($concurrentRequest); + + /* + * After 10s, the cached response has become stale. Yet, we're still within the "stale_while_revalidate" + * timeout so we may serve the stale response. + */ + sleep(10); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('stale-while-revalidate'); + $this->assertEquals('Old response', $this->response->getContent()); + + /* + * Another 10s later, stale_while_revalidate is over. Resort to serving the old response, but + * do so with a "server unavailable" message. + */ + sleep(10); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(503, $this->response->getStatusCode()); + $this->assertEquals('Old response', $this->response->getContent()); + } + + public function testHitsCachedResponseWithSMaxAgeDirective() + { + $time = \DateTime::createFromFormat('U', time() - 5); + $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 's-maxage=10, max-age=0')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformation() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=10/', $this->response->headers->get('Cache-Control')); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=10/', $this->response->headers->get('Cache-Control')); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformationAndAfterTtlWasExpired() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 2; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + // expires the cache + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time() - 5); + $tmp[0][1]['date'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp)); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->setNextResponse(); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformationAndAfterTtlWasExpiredWithStatus304() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 2; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + // expires the cache + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time() - 5); + $tmp[0][1]['date'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp)); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control')); + } + + public function testDoesNotAssignDefaultTtlWhenResponseHasMustRevalidateDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 'must-revalidate')); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertNotRegExp('/s-maxage/', $this->response->headers->get('Cache-Control')); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testFetchesFullResponseWhenCacheStaleAndNoValidatorsPresent() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertNotNull($this->response->headers->get('Age')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + // go in and play around with the cached metadata directly ... + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time()); + $tmp[0][1]['expires'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp)); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('stale'); + $this->assertTraceNotContains('fresh'); + $this->assertTraceNotContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testValidatesCachedResponsesWithLastModifiedAndNoFreshnessInformation() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time) { + $response->headers->set('Cache-Control', 'public'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + if ($time->format(DATE_RFC2822) == $request->headers->get('IF_MODIFIED_SINCE')) { + $response->setStatusCode(304); + $response->setContent(''); + } + }); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Last-Modified')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('stale'); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Last-Modified')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + } + + public function testValidatesCachedResponsesUseSameHttpMethod() + { + $test = $this; + + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($test) { + $test->assertSame('OPTIONS', $request->getMethod()); + }); + + // build initial request + $this->request('OPTIONS', '/'); + + // build subsequent request + $this->request('OPTIONS', '/'); + } + + public function testValidatesCachedResponsesWithETagAndNoFreshnessInformation() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->headers->set('Cache-Control', 'public'); + $response->headers->set('ETag', '"12345"'); + if ($response->getETag() == $request->headers->get('IF_NONE_MATCH')) { + $response->setStatusCode(304); + $response->setContent(''); + } + }); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('ETag')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('ETag')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + } + + public function testServesResponseWhileFreshAndRevalidatesWithLastModifiedInformation() + { + $time = \DateTime::createFromFormat('U', time()); + + $this->setNextResponse(200, array(), 'Hello World', function (Request $request, Response $response) use ($time) { + $response->setSharedMaxAge(10); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + }); + + // prime the cache + $this->request('GET', '/'); + + // next request before s-maxage has expired: Serve from cache + // without hitting the backend + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + sleep(15); // expire the cache + + $this->setNextResponse(304, array(), '', function (Request $request, Response $response) use ($time) { + $this->assertEquals($time->format(DATE_RFC2822), $request->headers->get('IF_MODIFIED_SINCE')); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + } + + public function testReplacesCachedResponsesWhenValidationResultsInNon304Response() + { + $time = \DateTime::createFromFormat('U', time()); + $count = 0; + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time, &$count) { + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + $response->headers->set('Cache-Control', 'public'); + switch (++$count) { + case 1: + $response->setContent('first response'); + break; + case 2: + $response->setContent('second response'); + break; + case 3: + $response->setContent(''); + $response->setStatusCode(304); + break; + } + }); + + // first request should fetch from backend and store in cache + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('first response', $this->response->getContent()); + + // second request is validated, is invalid, and replaces cached entry + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('second response', $this->response->getContent()); + + // third response is validated, valid, and returns cached entry + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('second response', $this->response->getContent()); + + $this->assertEquals(3, $count); + } + + public function testPassesHeadRequestsThroughDirectlyOnPass() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->setContent(''); + $response->setStatusCode(200); + $this->assertEquals('HEAD', $request->getMethod()); + }); + + $this->request('HEAD', '/', array('HTTP_EXPECT' => 'something ...')); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('', $this->response->getContent()); + } + + public function testUsesCacheToRespondToHeadRequestsWhenFresh() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->setContent('Hello World'); + $response->setStatusCode(200); + $this->assertNotEquals('HEAD', $request->getMethod()); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('HEAD', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->getContent()); + $this->assertEquals(strlen('Hello World'), $this->response->headers->get('Content-Length')); + } + + public function testSendsNoContentWhenFresh() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time) { + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->getContent()); + } + + public function testInvalidatesCachedResponsesOnPost() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + if ('GET' == $request->getMethod()) { + $response->setStatusCode(200); + $response->headers->set('Cache-Control', 'public, max-age=500'); + $response->setContent('Hello World'); + } elseif ('POST' == $request->getMethod()) { + $response->setStatusCode(303); + $response->headers->set('Location', '/'); + $response->headers->remove('Cache-Control'); + $response->setContent(''); + } + }); + + // build initial request to enter into the cache + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + // make sure it is valid + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + // now POST to same URL + $this->request('POST', '/helloworld'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('/', $this->response->headers->get('Location')); + $this->assertTraceContains('invalidate'); + $this->assertTraceContains('pass'); + $this->assertEquals('', $this->response->getContent()); + + // now make sure it was actually invalidated + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + } + + public function testServesFromCacheWhenHeadersMatch() + { + $count = 0; + $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) { + $response->headers->set('Vary', 'Accept User-Agent Foo'); + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('X-Response-Count', ++$count); + $response->setContent($request->headers->get('USER_AGENT')); + }); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + } + + public function testStoresMultipleResponsesWhenHeadersDiffer() + { + $count = 0; + $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) { + $response->headers->set('Vary', 'Accept User-Agent Foo'); + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('X-Response-Count', ++$count); + $response->setContent($request->headers->get('USER_AGENT')); + }); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertEquals(1, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(2, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertTraceContains('fresh'); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertEquals(1, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertTraceContains('fresh'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(2, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertTraceContains('miss'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(3, $this->response->headers->get('X-Response-Count')); + } + + public function testShouldCatchExceptions() + { + $this->catchExceptions(); + + $this->setNextResponse(); + $this->request('GET', '/'); + + $this->assertExceptionsAreCaught(); + } + + public function testShouldCatchExceptionsWhenReloadingAndNoCacheRequest() + { + $this->catchExceptions(); + + $this->setNextResponse(); + $this->cacheConfig['allow_reload'] = true; + $this->request('GET', '/', array(), array(), false, array('Pragma' => 'no-cache')); + + $this->assertExceptionsAreCaught(); + } + + public function testShouldNotCatchExceptions() + { + $this->catchExceptions(false); + + $this->setNextResponse(); + $this->request('GET', '/'); + + $this->assertExceptionsAreNotCaught(); + } + + public function testEsiCacheSendsTheLowestTtl() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('Cache-Control' => 's-maxage=300'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); + + // check for 100 or 99 as the test can be executed after a second change + $this->assertTrue(in_array($this->response->getTtl(), array(99, 100))); + } + + public function testEsiCacheForceValidation() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('ETag' => 'foobar'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); + $this->assertNull($this->response->getTtl()); + $this->assertTrue($this->response->mustRevalidate()); + $this->assertTrue($this->response->headers->hasCacheControlDirective('private')); + $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache')); + } + + public function testEsiRecalculateContentLengthHeader() + { + $responses = array( + array( + 'status' => 200, + 'body' => '', + 'headers' => array( + 'Content-Length' => 26, + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array(), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World!', $this->response->getContent()); + $this->assertEquals(12, $this->response->headers->get('Content-Length')); + } + + public function testClientIpIsAlwaysLocalhostForForwardedRequests() + { + $this->setNextResponse(); + $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); + + $this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR')); + } + + /** + * @dataProvider getTrustedProxyData + */ + public function testHttpCacheIsSetAsATrustedProxy(array $existing, array $expected) + { + Request::setTrustedProxies($existing, Request::HEADER_X_FORWARDED_ALL); + + $this->setNextResponse(); + $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); + + $this->assertEquals($expected, Request::getTrustedProxies()); + } + + public function getTrustedProxyData() + { + return array( + array(array(), array('127.0.0.1')), + array(array('10.0.0.2'), array('10.0.0.2', '127.0.0.1')), + array(array('10.0.0.2', '127.0.0.1'), array('10.0.0.2', '127.0.0.1')), + ); + } + + /** + * @dataProvider getXForwardedForData + */ + public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected) + { + $this->setNextResponse(); + $server = array('REMOTE_ADDR' => '10.0.0.1'); + if (false !== $xForwardedFor) { + $server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor; + } + $this->request('GET', '/', $server); + + $this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + } + + public function getXForwardedForData() + { + return array( + array(false, '10.0.0.1'), + array('10.0.0.2', '10.0.0.2, 10.0.0.1'), + array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'), + ); + } + + public function testXForwarderForHeaderForPassRequests() + { + $this->setNextResponse(); + $server = array('REMOTE_ADDR' => '10.0.0.1'); + $this->request('POST', '/', $server); + + $this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + } + + public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses() + { + $time = \DateTime::createFromFormat('U', time()); + + $responses = array( + array( + 'status' => 200, + 'body' => '', + 'headers' => array( + 'Surrogate-Control' => 'content="ESI/1.0"', + 'ETag' => 'hey', + 'Last-Modified' => $time->format(DATE_RFC2822), + ), + ), + array( + 'status' => 200, + 'body' => 'Hey!', + 'headers' => array(), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertNull($this->response->getETag()); + $this->assertNull($this->response->getLastModified()); + } + + public function testDoesNotCacheOptionsRequest() + { + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'get'); + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + + $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'options'); + $this->request('OPTIONS', '/'); + $this->assertHttpKernelIsCalled(); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertSame('get', $this->response->getContent()); + } +} + +class TestKernel implements HttpKernelInterface +{ + public $terminateCalled = false; + + public function terminate(Request $request, Response $response) + { + $this->terminateCalled = true; + } + + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php new file mode 100644 index 00000000..ed5c690d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class HttpCacheTestCase extends TestCase +{ + protected $kernel; + protected $cache; + protected $caches; + protected $cacheConfig; + protected $request; + protected $response; + protected $responses; + protected $catch; + protected $esi; + + /** + * @var Store + */ + protected $store; + + protected function setUp() + { + $this->kernel = null; + + $this->cache = null; + $this->esi = null; + $this->caches = array(); + $this->cacheConfig = array(); + + $this->request = null; + $this->response = null; + $this->responses = array(); + + $this->catch = false; + + $this->clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + protected function tearDown() + { + if ($this->cache) { + $this->cache->getStore()->cleanup(); + } + $this->kernel = null; + $this->cache = null; + $this->caches = null; + $this->request = null; + $this->response = null; + $this->responses = null; + $this->cacheConfig = null; + $this->catch = null; + $this->esi = null; + + $this->clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + public function assertHttpKernelIsCalled() + { + $this->assertTrue($this->kernel->hasBeenCalled()); + } + + public function assertHttpKernelIsNotCalled() + { + $this->assertFalse($this->kernel->hasBeenCalled()); + } + + public function assertResponseOk() + { + $this->assertEquals(200, $this->response->getStatusCode()); + } + + public function assertTraceContains($trace) + { + $traces = $this->cache->getTraces(); + $traces = current($traces); + + $this->assertRegExp('/'.$trace.'/', implode(', ', $traces)); + } + + public function assertTraceNotContains($trace) + { + $traces = $this->cache->getTraces(); + $traces = current($traces); + + $this->assertNotRegExp('/'.$trace.'/', implode(', ', $traces)); + } + + public function assertExceptionsAreCaught() + { + $this->assertTrue($this->kernel->isCatchingExceptions()); + } + + public function assertExceptionsAreNotCaught() + { + $this->assertFalse($this->kernel->isCatchingExceptions()); + } + + public function request($method, $uri = '/', $server = array(), $cookies = array(), $esi = false, $headers = array()) + { + if (null === $this->kernel) { + throw new \LogicException('You must call setNextResponse() before calling request().'); + } + + $this->kernel->reset(); + + $this->store = new Store(sys_get_temp_dir().'/http_cache'); + + $this->cacheConfig['debug'] = true; + + $this->esi = $esi ? new Esi() : null; + $this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig); + $this->request = Request::create($uri, $method, array(), $cookies, array(), $server); + $this->request->headers->add($headers); + + $this->response = $this->cache->handle($this->request, HttpKernelInterface::MASTER_REQUEST, $this->catch); + + $this->responses[] = $this->response; + } + + public function getMetaStorageValues() + { + $values = array(); + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(sys_get_temp_dir().'/http_cache/md', \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + $values[] = file_get_contents($file); + } + + return $values; + } + + // A basic response with 200 status code and a tiny body. + public function setNextResponse($statusCode = 200, array $headers = array(), $body = 'Hello World', \Closure $customizer = null) + { + $this->kernel = new TestHttpKernel($body, $statusCode, $headers, $customizer); + } + + public function setNextResponses($responses) + { + $this->kernel = new TestMultipleHttpKernel($responses); + } + + public function catchExceptions($catch = true) + { + $this->catch = $catch; + } + + public static function clearDirectory($directory) + { + if (!is_dir($directory)) { + return; + } + + $fp = opendir($directory); + while (false !== $file = readdir($fp)) { + if (!in_array($file, array('.', '..'))) { + if (is_link($directory.'/'.$file)) { + unlink($directory.'/'.$file); + } elseif (is_dir($directory.'/'.$file)) { + self::clearDirectory($directory.'/'.$file); + rmdir($directory.'/'.$file); + } else { + unlink($directory.'/'.$file); + } + } + } + + closedir($fp); + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php new file mode 100644 index 00000000..f30a3b6a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php @@ -0,0 +1,78 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategy; + +class ResponseCacheStrategyTest extends TestCase +{ + public function testMinimumSharedMaxAgeWins() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $response1 = new Response(); + $response1->setSharedMaxAge(60); + $cacheStrategy->add($response1); + + $response2 = new Response(); + $response2->setSharedMaxAge(3600); + $cacheStrategy->add($response2); + + $response = new Response(); + $response->setSharedMaxAge(86400); + $cacheStrategy->update($response); + + $this->assertSame('60', $response->headers->getCacheControlDirective('s-maxage')); + } + + public function testSharedMaxAgeNotSetIfNotSetInAnyEmbeddedRequest() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $response1 = new Response(); + $response1->setSharedMaxAge(60); + $cacheStrategy->add($response1); + + $response2 = new Response(); + $cacheStrategy->add($response2); + + $response = new Response(); + $response->setSharedMaxAge(86400); + $cacheStrategy->update($response); + + $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage')); + } + + public function testSharedMaxAgeNotSetIfNotSetInMasterRequest() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $response1 = new Response(); + $response1->setSharedMaxAge(60); + $cacheStrategy->add($response1); + + $response2 = new Response(); + $response2->setSharedMaxAge(3600); + $cacheStrategy->add($response2); + + $response = new Response(); + $cacheStrategy->update($response); + + $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage')); + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php new file mode 100644 index 00000000..1079d37a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Ssi; + +class SsiTest extends TestCase +{ + public function testHasSurrogateSsiCapability() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="SSI/1.0"'); + $this->assertTrue($ssi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'foobar'); + $this->assertFalse($ssi->hasSurrogateCapability($request)); + + $request = Request::create('/'); + $this->assertFalse($ssi->hasSurrogateCapability($request)); + } + + public function testAddSurrogateSsiCapability() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $ssi->addSurrogateCapability($request); + $this->assertEquals('symfony="SSI/1.0"', $request->headers->get('Surrogate-Capability')); + + $ssi->addSurrogateCapability($request); + $this->assertEquals('symfony="SSI/1.0", symfony="SSI/1.0"', $request->headers->get('Surrogate-Capability')); + } + + public function testAddSurrogateControl() + { + $ssi = new Ssi(); + + $response = new Response('foo '); + $ssi->addSurrogateControl($response); + $this->assertEquals('content="SSI/1.0"', $response->headers->get('Surrogate-Control')); + + $response = new Response('foo'); + $ssi->addSurrogateControl($response); + $this->assertEquals('', $response->headers->get('Surrogate-Control')); + } + + public function testNeedsSsiParsing() + { + $ssi = new Ssi(); + + $response = new Response(); + $response->headers->set('Surrogate-Control', 'content="SSI/1.0"'); + $this->assertTrue($ssi->needsParsing($response)); + + $response = new Response(); + $this->assertFalse($ssi->needsParsing($response)); + } + + public function testRenderIncludeTag() + { + $ssi = new Ssi(); + + $this->assertEquals('', $ssi->renderIncludeTag('/', '/alt', true)); + $this->assertEquals('', $ssi->renderIncludeTag('/', '/alt', false)); + $this->assertEquals('', $ssi->renderIncludeTag('/')); + } + + public function testProcessDoesNothingIfContentTypeIsNotHtml() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response(); + $response->headers->set('Content-Type', 'text/plain'); + $ssi->process($request, $response); + + $this->assertFalse($response->headers->has('x-body-eval')); + } + + public function testProcess() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $ssi->process($request, $response); + + $this->assertEquals('foo surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + + $response = new Response('foo '); + $ssi->process($request, $response); + + $this->assertEquals("foo surrogate->handle(\$this, 'foo\\'', '', false) ?>"."\n", $response->getContent()); + } + + public function testProcessEscapesPhpTags() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response(''); + $ssi->process($request, $response); + + $this->assertEquals('php cript language=php>', $response->getContent()); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessWhenNoSrcInAnSsi() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $ssi->process($request, $response); + } + + public function testProcessRemoveSurrogateControlHeader() + { + $ssi = new Ssi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $response->headers->set('Surrogate-Control', 'content="SSI/1.0"'); + $ssi->process($request, $response); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + + $response->headers->set('Surrogate-Control', 'no-store, content="SSI/1.0"'); + $ssi->process($request, $response); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + + $response->headers->set('Surrogate-Control', 'content="SSI/1.0", no-store'); + $ssi->process($request, $response); + $this->assertEquals('SSI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + } + + public function testHandle() + { + $ssi = new Ssi(); + $cache = $this->getCache(Request::create('/'), new Response('foo')); + $this->assertEquals('foo', $ssi->handle($cache, '/', '/alt', true)); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenResponseIsNot200() + { + $ssi = new Ssi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $ssi->handle($cache, '/', '/alt', false); + } + + public function testHandleWhenResponseIsNot200AndErrorsAreIgnored() + { + $ssi = new Ssi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $this->assertEquals('', $ssi->handle($cache, '/', '/alt', true)); + } + + public function testHandleWhenResponseIsNot200AndAltIsPresent() + { + $ssi = new Ssi(); + $response1 = new Response('foo'); + $response1->setStatusCode(404); + $response2 = new Response('bar'); + $cache = $this->getCache(Request::create('/'), array($response1, $response2)); + $this->assertEquals('bar', $ssi->handle($cache, '/', '/alt', false)); + } + + protected function getCache($request, $response) + { + $cache = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpCache\HttpCache')->setMethods(array('getRequest', 'handle'))->disableOriginalConstructor()->getMock(); + $cache->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + if (is_array($response)) { + $cache->expects($this->any()) + ->method('handle') + ->will(call_user_func_array(array($this, 'onConsecutiveCalls'), $response)) + ; + } else { + $cache->expects($this->any()) + ->method('handle') + ->will($this->returnValue($response)) + ; + } + + return $cache; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php new file mode 100644 index 00000000..cef01916 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Store; + +class StoreTest extends TestCase +{ + protected $request; + protected $response; + + /** + * @var Store + */ + protected $store; + + protected function setUp() + { + $this->request = Request::create('/'); + $this->response = new Response('hello world', 200, array()); + + HttpCacheTestCase::clearDirectory(sys_get_temp_dir().'/http_cache'); + + $this->store = new Store(sys_get_temp_dir().'/http_cache'); + } + + protected function tearDown() + { + $this->store = null; + $this->request = null; + $this->response = null; + + HttpCacheTestCase::clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + public function testReadsAnEmptyArrayWithReadWhenNothingCachedAtKey() + { + $this->assertEmpty($this->getStoreMetadata('/nothing')); + } + + public function testUnlockFileThatDoesExist() + { + $cacheKey = $this->storeSimpleEntry(); + $this->store->lock($this->request); + + $this->assertTrue($this->store->unlock($this->request)); + } + + public function testUnlockFileThatDoesNotExist() + { + $this->assertFalse($this->store->unlock($this->request)); + } + + public function testRemovesEntriesForKeyWithPurge() + { + $request = Request::create('/foo'); + $this->store->write($request, new Response('foo')); + + $metadata = $this->getStoreMetadata($request); + $this->assertNotEmpty($metadata); + + $this->assertTrue($this->store->purge('/foo')); + $this->assertEmpty($this->getStoreMetadata($request)); + + // cached content should be kept after purging + $path = $this->store->getPath($metadata[0][1]['x-content-digest'][0]); + $this->assertTrue(is_file($path)); + + $this->assertFalse($this->store->purge('/bar')); + } + + public function testStoresACacheEntry() + { + $cacheKey = $this->storeSimpleEntry(); + + $this->assertNotEmpty($this->getStoreMetadata($cacheKey)); + } + + public function testSetsTheXContentDigestResponseHeaderBeforeStoring() + { + $cacheKey = $this->storeSimpleEntry(); + $entries = $this->getStoreMetadata($cacheKey); + list($req, $res) = $entries[0]; + + $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); + } + + public function testFindsAStoredEntryWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + + $this->assertNotNull($response); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + } + + public function testDoesNotFindAnEntryWithLookupWhenNoneExists() + { + $request = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + + $this->assertNull($this->store->lookup($request)); + } + + public function testCanonizesUrlsForCacheKeys() + { + $this->storeSimpleEntry($path = '/test?x=y&p=q'); + $hitsReq = Request::create($path); + $missReq = Request::create('/test?p=x'); + + $this->assertNotNull($this->store->lookup($hitsReq)); + $this->assertNull($this->store->lookup($missReq)); + } + + public function testDoesNotFindAnEntryWithLookupWhenTheBodyDoesNotExist() + { + $this->storeSimpleEntry(); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $path = $this->getStorePath($this->response->headers->get('X-Content-Digest')); + @unlink($path); + $this->assertNull($this->store->lookup($this->request)); + } + + public function testRestoresResponseHeadersProperlyWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + + $this->assertEquals($response->headers->all(), array_merge(array('content-length' => 4, 'x-body-file' => array($this->getStorePath($response->headers->get('X-Content-Digest')))), $this->response->headers->all())); + } + + public function testRestoresResponseContentFromEntityStoreWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test')), $response->getContent()); + } + + public function testInvalidatesMetaAndEntityStoreEntriesWithInvalidate() + { + $this->storeSimpleEntry(); + $this->store->invalidate($this->request); + $response = $this->store->lookup($this->request); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertFalse($response->isFresh()); + } + + public function testSucceedsQuietlyWhenInvalidateCalledWithNoMatchingEntries() + { + $req = Request::create('/test'); + $this->store->invalidate($req); + $this->assertNull($this->store->lookup($this->request)); + } + + public function testDoesNotReturnEntriesThatVaryWithLookup() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res = new Response('test', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req1, $res); + + $this->assertNull($this->store->lookup($req2)); + } + + public function testDoesNotReturnEntriesThatSlightlyVaryWithLookup() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bam')); + $res = new Response('test', 200, array('Vary' => array('Foo', 'Bar'))); + $this->store->write($req1, $res); + + $this->assertNull($this->store->lookup($req2)); + } + + public function testStoresMultipleResponsesForEachVaryCombination() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res1 = new Response('test 1', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req1, $res1); + + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res2 = new Response('test 2', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req2, $res2); + + $req3 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom')); + $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req3, $res3); + + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent()); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent()); + + $this->assertCount(3, $this->getStoreMetadata($key)); + } + + public function testOverwritesNonVaryingResponseWithStore() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res1 = new Response('test 1', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req1, $res1); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 1')), $this->store->lookup($req1)->getContent()); + + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res2 = new Response('test 2', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req2, $res2); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 2')), $this->store->lookup($req2)->getContent()); + + $req3 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req3, $res3); + $this->assertEquals($this->getStorePath('en'.hash('sha256', 'test 3')), $this->store->lookup($req3)->getContent()); + + $this->assertCount(2, $this->getStoreMetadata($key)); + } + + public function testLocking() + { + $req = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $this->assertTrue($this->store->lock($req)); + + $path = $this->store->lock($req); + $this->assertTrue($this->store->isLocked($req)); + + $this->store->unlock($req); + $this->assertFalse($this->store->isLocked($req)); + } + + public function testPurgeHttps() + { + $request = Request::create('https://example.com/foo'); + $this->store->write($request, new Response('foo')); + + $this->assertNotEmpty($this->getStoreMetadata($request)); + + $this->assertTrue($this->store->purge('https://example.com/foo')); + $this->assertEmpty($this->getStoreMetadata($request)); + } + + public function testPurgeHttpAndHttps() + { + $requestHttp = Request::create('https://example.com/foo'); + $this->store->write($requestHttp, new Response('foo')); + + $requestHttps = Request::create('http://example.com/foo'); + $this->store->write($requestHttps, new Response('foo')); + + $this->assertNotEmpty($this->getStoreMetadata($requestHttp)); + $this->assertNotEmpty($this->getStoreMetadata($requestHttps)); + + $this->assertTrue($this->store->purge('http://example.com/foo')); + $this->assertEmpty($this->getStoreMetadata($requestHttp)); + $this->assertEmpty($this->getStoreMetadata($requestHttps)); + } + + protected function storeSimpleEntry($path = null, $headers = array()) + { + if (null === $path) { + $path = '/test'; + } + + $this->request = Request::create($path, 'get', array(), array(), array(), $headers); + $this->response = new Response('test', 200, array('Cache-Control' => 'max-age=420')); + + return $this->store->write($this->request, $this->response); + } + + protected function getStoreMetadata($key) + { + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('getMetadata'); + $m->setAccessible(true); + + if ($key instanceof Request) { + $m1 = $r->getMethod('getCacheKey'); + $m1->setAccessible(true); + $key = $m1->invoke($this->store, $key); + } + + return $m->invoke($this->store, $key); + } + + protected function getStorePath($key) + { + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('getPath'); + $m->setAccessible(true); + + return $m->invoke($this->store, $key); + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php b/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php new file mode 100644 index 00000000..946c7a31 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestHttpKernel extends HttpKernel implements ControllerResolverInterface, ArgumentResolverInterface +{ + protected $body; + protected $status; + protected $headers; + protected $called = false; + protected $customizer; + protected $catch = false; + protected $backendRequest; + + public function __construct($body, $status, $headers, \Closure $customizer = null) + { + $this->body = $body; + $this->status = $status; + $this->headers = $headers; + $this->customizer = $customizer; + + parent::__construct(new EventDispatcher(), $this, null, $this); + } + + public function getBackendRequest() + { + return $this->backendRequest; + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + $this->catch = $catch; + $this->backendRequest = $request; + + return parent::handle($request, $type, $catch); + } + + public function isCatchingExceptions() + { + return $this->catch; + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response($this->body, $this->status, $this->headers); + + if (null !== $customizer = $this->customizer) { + $customizer($request, $response); + } + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->called = false; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php b/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.php new file mode 100644 index 00000000..926d8daf --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.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\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestMultipleHttpKernel extends HttpKernel implements ControllerResolverInterface, ArgumentResolverInterface +{ + protected $bodies = array(); + protected $statuses = array(); + protected $headers = array(); + protected $called = false; + protected $backendRequest; + + public function __construct($responses) + { + foreach ($responses as $response) { + $this->bodies[] = $response['body']; + $this->statuses[] = $response['status']; + $this->headers[] = $response['headers']; + } + + parent::__construct(new EventDispatcher(), $this, null, $this); + } + + public function getBackendRequest() + { + return $this->backendRequest; + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + $this->backendRequest = $request; + + return parent::handle($request, $type, $catch); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response(array_shift($this->bodies), array_shift($this->statuses), array_shift($this->headers)); + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->called = false; + } +} diff --git a/vendor/symfony/http-kernel/Tests/HttpKernelTest.php b/vendor/symfony/http-kernel/Tests/HttpKernelTest.php new file mode 100644 index 00000000..7aed26aa --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpKernelTest.php @@ -0,0 +1,403 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class HttpKernelTest extends TestCase +{ + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrue() + { + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }); + + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsFalseAndNoListenerIsRegistered() + { + $kernel = $this->getHttpKernel(new EventDispatcher(), function () { throw new \RuntimeException(); }); + + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, false); + } + + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrueWithAHandlingListener() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new Response($event->getException()->getMessage())); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException('foo'); }); + $response = $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + + $this->assertEquals('500', $response->getStatusCode()); + $this->assertEquals('foo', $response->getContent()); + } + + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrueWithANonHandlingListener() + { + $exception = new \RuntimeException(); + + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + // should set a response, but does not + }); + + $kernel = $this->getHttpKernel($dispatcher, function () use ($exception) { throw $exception; }); + + try { + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + $this->fail('LogicException expected'); + } catch (\RuntimeException $e) { + $this->assertSame($exception, $e); + } + } + + public function testHandleExceptionWithARedirectionResponse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new RedirectResponse('/login', 301)); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new AccessDeniedHttpException(); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals('301', $response->getStatusCode()); + $this->assertEquals('/login', $response->headers->get('Location')); + } + + public function testHandleHttpException() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new Response($event->getException()->getMessage())); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new MethodNotAllowedHttpException(array('POST')); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals('405', $response->getStatusCode()); + $this->assertEquals('POST', $response->headers->get('Allow')); + } + + /** + * @group legacy + * @dataProvider getStatusCodes + */ + public function testLegacyHandleWhenAnExceptionIsHandledWithASpecificStatusCode($responseStatusCode, $expectedStatusCode) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) use ($responseStatusCode, $expectedStatusCode) { + $event->setResponse(new Response('', $responseStatusCode, array('X-Status-Code' => $expectedStatusCode))); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException(); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals($expectedStatusCode, $response->getStatusCode()); + $this->assertFalse($response->headers->has('X-Status-Code')); + } + + public function getStatusCodes() + { + return array( + array(200, 404), + array(404, 200), + array(301, 200), + array(500, 200), + ); + } + + /** + * @dataProvider getSpecificStatusCodes + */ + public function testHandleWhenAnExceptionIsHandledWithASpecificStatusCode($expectedStatusCode) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function (GetResponseForExceptionEvent $event) use ($expectedStatusCode) { + $event->allowCustomResponseCode(); + $event->setResponse(new Response('', $expectedStatusCode)); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { throw new \RuntimeException(); }); + $response = $kernel->handle(new Request()); + + $this->assertEquals($expectedStatusCode, $response->getStatusCode()); + } + + public function getSpecificStatusCodes() + { + return array( + array(200), + array(302), + array(403), + ); + } + + public function testHandleWhenAListenerReturnsAResponse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->setResponse(new Response('hello')); + }); + + $kernel = $this->getHttpKernel($dispatcher); + + $this->assertEquals('hello', $kernel->handle(new Request())->getContent()); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testHandleWhenNoControllerIsFound() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, false); + + $kernel->handle(new Request()); + } + + public function testHandleWhenTheControllerIsAClosure() + { + $response = new Response('foo'); + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, function () use ($response) { return $response; }); + + $this->assertSame($response, $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnObjectWithInvoke() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, new Controller()); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAFunction() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, 'Symfony\Component\HttpKernel\Tests\controller_func'); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnArray() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, array(new Controller(), 'controller')); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAStaticArray() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, array('Symfony\Component\HttpKernel\Tests\Controller', 'staticcontroller')); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + /** + * @expectedException \LogicException + */ + public function testHandleWhenTheControllerDoesNotReturnAResponse() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, function () { return 'foo'; }); + + $kernel->handle(new Request()); + } + + public function testHandleWhenTheControllerDoesNotReturnAResponseButAViewIsRegistered() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::VIEW, function ($event) { + $event->setResponse(new Response($event->getControllerResult())); + }); + + $kernel = $this->getHttpKernel($dispatcher, function () { return 'foo'; }); + + $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); + } + + public function testHandleWithAResponseListener() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::RESPONSE, function ($event) { + $event->setResponse(new Response('foo')); + }); + $kernel = $this->getHttpKernel($dispatcher); + + $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); + } + + public function testHandleAllowChangingControllerArguments() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::CONTROLLER_ARGUMENTS, function (FilterControllerArgumentsEvent $event) { + $event->setArguments(array('foo')); + }); + + $kernel = $this->getHttpKernel($dispatcher, function ($content) { return new Response($content); }); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleAllowChangingControllerAndArguments() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::CONTROLLER_ARGUMENTS, function (FilterControllerArgumentsEvent $event) { + $oldController = $event->getController(); + $oldArguments = $event->getArguments(); + + $newController = function ($id) use ($oldController, $oldArguments) { + $response = call_user_func_array($oldController, $oldArguments); + + $response->headers->set('X-Id', $id); + + return $response; + }; + + $event->setController($newController); + $event->setArguments(array('bar')); + }); + + $kernel = $this->getHttpKernel($dispatcher, function ($content) { return new Response($content); }, null, array('foo')); + + $this->assertResponseEquals(new Response('foo', 200, array('X-Id' => 'bar')), $kernel->handle(new Request())); + } + + public function testTerminate() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher); + $dispatcher->addListener(KernelEvents::TERMINATE, function ($event) use (&$called, &$capturedKernel, &$capturedRequest, &$capturedResponse) { + $called = true; + $capturedKernel = $event->getKernel(); + $capturedRequest = $event->getRequest(); + $capturedResponse = $event->getResponse(); + }); + + $kernel->terminate($request = Request::create('/'), $response = new Response()); + $this->assertTrue($called); + $this->assertEquals($kernel, $capturedKernel); + $this->assertEquals($request, $capturedRequest); + $this->assertEquals($response, $capturedResponse); + } + + public function testVerifyRequestStackPushPopDuringHandle() + { + $request = new Request(); + + $stack = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->setMethods(array('push', 'pop'))->getMock(); + $stack->expects($this->at(0))->method('push')->with($this->equalTo($request)); + $stack->expects($this->at(1))->method('pop'); + + $dispatcher = new EventDispatcher(); + $kernel = $this->getHttpKernel($dispatcher, null, $stack); + + $kernel->handle($request, HttpKernelInterface::MASTER_REQUEST); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + public function testInconsistentClientIpsOnMasterRequests() + { + $request = new Request(); + $request->setTrustedProxies(array('1.1.1.1'), Request::HEADER_X_FORWARDED_FOR | Request::HEADER_FORWARDED); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('FORWARDED', 'for=2.2.2.2'); + $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); + + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->getRequest()->getClientIp(); + }); + + $kernel = $this->getHttpKernel($dispatcher); + $kernel->handle($request, $kernel::MASTER_REQUEST, false); + } + + private function getHttpKernel(EventDispatcherInterface $eventDispatcher, $controller = null, RequestStack $requestStack = null, array $arguments = array()) + { + if (null === $controller) { + $controller = function () { return new Response('Hello'); }; + } + + $controllerResolver = $this->getMockBuilder(ControllerResolverInterface::class)->getMock(); + $controllerResolver + ->expects($this->any()) + ->method('getController') + ->will($this->returnValue($controller)); + + $argumentResolver = $this->getMockBuilder(ArgumentResolverInterface::class)->getMock(); + $argumentResolver + ->expects($this->any()) + ->method('getArguments') + ->will($this->returnValue($arguments)); + + return new HttpKernel($eventDispatcher, $controllerResolver, $requestStack, $argumentResolver); + } + + private function assertResponseEquals(Response $expected, Response $actual) + { + $expected->setDate($actual->getDate()); + $this->assertEquals($expected, $actual); + } +} + +class Controller +{ + public function __invoke() + { + return new Response('foo'); + } + + public function controller() + { + return new Response('foo'); + } + + public static function staticController() + { + return new Response('foo'); + } +} + +function controller_func() +{ + return new Response('foo'); +} diff --git a/vendor/symfony/http-kernel/Tests/KernelTest.php b/vendor/symfony/http-kernel/Tests/KernelTest.php new file mode 100644 index 00000000..d2a27d03 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/KernelTest.php @@ -0,0 +1,900 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForOverrideName; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelWithoutBundles; + +class KernelTest extends TestCase +{ + public function testConstructor() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $this->assertEquals($env, $kernel->getEnvironment()); + $this->assertEquals($debug, $kernel->isDebug()); + $this->assertFalse($kernel->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $kernel->getStartTime()); + $this->assertNull($kernel->getContainer()); + } + + public function testClone() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $clone = clone $kernel; + + $this->assertEquals($env, $clone->getEnvironment()); + $this->assertEquals($debug, $clone->isDebug()); + $this->assertFalse($clone->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $clone->getStartTime()); + $this->assertNull($clone->getContainer()); + } + + public function testBootInitializesBundlesAndContainer() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer')); + $kernel->expects($this->once()) + ->method('initializeBundles'); + $kernel->expects($this->once()) + ->method('initializeContainer'); + + $kernel->boot(); + } + + public function testBootSetsTheContainerToTheBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle')->getMock(); + $bundle->expects($this->once()) + ->method('setContainer'); + + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'getBundles')); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->boot(); + } + + public function testBootSetsTheBootedFlagToTrue() + { + // use test kernel to access isBooted() + $kernel = $this->getKernelForTest(array('initializeBundles', 'initializeContainer')); + $kernel->boot(); + + $this->assertTrue($kernel->isBooted()); + } + + /** + * @group legacy + */ + public function testClassCacheIsLoaded() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'doLoadClassCache')); + $kernel->loadClassCache('name', '.extension'); + $kernel->expects($this->once()) + ->method('doLoadClassCache') + ->with('name', '.extension'); + + $kernel->boot(); + } + + public function testClassCacheIsNotLoadedByDefault() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'doLoadClassCache')); + $kernel->expects($this->never()) + ->method('doLoadClassCache'); + + $kernel->boot(); + } + + /** + * @group legacy + */ + public function testClassCacheIsNotLoadedWhenKernelIsNotBooted() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer', 'doLoadClassCache')); + $kernel->loadClassCache(); + $kernel->expects($this->never()) + ->method('doLoadClassCache'); + } + + public function testEnvParametersResourceIsAdded() + { + $container = new ContainerBuilder(); + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getContainerBuilder', 'prepareContainer', 'getCacheDir', 'getLogDir')) + ->getMock(); + $kernel->expects($this->any()) + ->method('getContainerBuilder') + ->will($this->returnValue($container)); + $kernel->expects($this->any()) + ->method('prepareContainer') + ->will($this->returnValue(null)); + $kernel->expects($this->any()) + ->method('getCacheDir') + ->will($this->returnValue(sys_get_temp_dir())); + $kernel->expects($this->any()) + ->method('getLogDir') + ->will($this->returnValue(sys_get_temp_dir())); + + $reflection = new \ReflectionClass(get_class($kernel)); + $method = $reflection->getMethod('buildContainer'); + $method->setAccessible(true); + $method->invoke($kernel); + + $found = false; + foreach ($container->getResources() as $resource) { + if ($resource instanceof EnvParametersResource) { + $found = true; + break; + } + } + + $this->assertTrue($found); + } + + public function testBootKernelSeveralTimesOnlyInitializesBundlesOnce() + { + $kernel = $this->getKernel(array('initializeBundles', 'initializeContainer')); + $kernel->expects($this->once()) + ->method('initializeBundles'); + + $kernel->boot(); + $kernel->boot(); + } + + public function testShutdownCallsShutdownOnAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle')->getMock(); + $bundle->expects($this->once()) + ->method('shutdown'); + + $kernel = $this->getKernel(array(), array($bundle)); + + $kernel->boot(); + $kernel->shutdown(); + } + + public function testShutdownGivesNullContainerToAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle')->getMock(); + $bundle->expects($this->at(3)) + ->method('setContainer') + ->with(null); + + $kernel = $this->getKernel(array('getBundles')); + $kernel->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->boot(); + $kernel->shutdown(); + } + + public function testHandleCallsHandleOnHttpKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + $httpKernelMock + ->expects($this->once()) + ->method('handle') + ->with($request, $type, $catch); + + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->handle($request, $type, $catch); + } + + public function testHandleBootsTheKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + + $kernel = $this->getKernel(array('getHttpKernel', 'boot')); + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->expects($this->once()) + ->method('boot'); + + $kernel->handle($request, $type, $catch); + } + + public function testStripComments() + { + $source = <<<'EOF' +assertEquals($expected, $output); + } + + public function testGetRootDir() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures', realpath($kernel->getRootDir())); + } + + public function testGetName() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals('Fixtures', $kernel->getName()); + } + + public function testOverrideGetName() + { + $kernel = new KernelForOverrideName('test', true); + + $this->assertEquals('overridden', $kernel->getName()); + } + + public function testSerialize() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $expected = serialize(array($env, $debug)); + $this->assertEquals($expected, $kernel->serialize()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenNameIsNotValid() + { + $this->getKernel()->locateResource('Foo'); + } + + /** + * @expectedException \RuntimeException + */ + public function testLocateResourceThrowsExceptionWhenNameIsUnsafe() + { + $this->getKernel()->locateResource('@FooBundle/../bar'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenBundleDoesNotExist() + { + $this->getKernel()->locateResource('@FooBundle/config/routing.xml'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenResourceDoesNotExist() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $kernel->locateResource('@Bundle1Bundle/config/routing.xml'); + } + + public function testLocateResourceReturnsTheFirstThatMatches() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $this->assertEquals(__DIR__.'/Fixtures/Bundle1Bundle/foo.txt', $kernel->locateResource('@Bundle1Bundle/foo.txt')); + } + + public function testLocateResourceReturnsTheFirstThatMatchesWithParent() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/Bundle2Bundle'); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(__DIR__.'/Fixtures/Bundle2Bundle/foo.txt', $kernel->locateResource('@ParentAABundle/foo.txt')); + $this->assertEquals(__DIR__.'/Fixtures/Bundle1Bundle/bar.txt', $kernel->locateResource('@ParentAABundle/bar.txt')); + } + + public function testLocateResourceReturnsAllMatches() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/Bundle2Bundle'); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Bundle2Bundle/foo.txt', + __DIR__.'/Fixtures/Bundle1Bundle/foo.txt', ), + $kernel->locateResource('@Bundle1Bundle/foo.txt', null, false)); + } + + public function testLocateResourceReturnsAllMatchesBis() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array( + $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'), + $this->getBundle(__DIR__.'/Foobar'), + ))) + ; + + $this->assertEquals( + array(__DIR__.'/Fixtures/Bundle1Bundle/foo.txt'), + $kernel->locateResource('@Bundle1Bundle/foo.txt', null, false) + ); + } + + public function testLocateResourceIgnoresDirOnNonResource() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/foo.txt', + $kernel->locateResource('@Bundle1Bundle/foo.txt', __DIR__.'/Fixtures') + ); + } + + public function testLocateResourceReturnsTheDirOneForResources() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/FooBundle', null, null, 'FooBundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle/foo.txt', + $kernel->locateResource('@FooBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources') + ); + } + + public function testLocateResourceReturnsTheDirOneForResourcesAndBundleOnes() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle', null, null, 'Bundle1Bundle')))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Resources/Bundle1Bundle/foo.txt', + __DIR__.'/Fixtures/Bundle1Bundle/Resources/foo.txt', ), + $kernel->locateResource('@Bundle1Bundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources', false) + ); + } + + public function testLocateResourceOverrideBundleAndResourcesFolders() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/BaseBundle', null, 'BaseBundle', 'BaseBundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/ChildBundle', 'ParentBundle', 'ChildBundle', 'ChildBundle'); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(4)) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Resources/ChildBundle/foo.txt', + __DIR__.'/Fixtures/ChildBundle/Resources/foo.txt', + __DIR__.'/Fixtures/BaseBundle/Resources/foo.txt', + ), + $kernel->locateResource('@BaseBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources', false) + ); + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/ChildBundle/foo.txt', + $kernel->locateResource('@BaseBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources') + ); + + try { + $kernel->locateResource('@BaseBundle/Resources/hide.txt', __DIR__.'/Fixtures/Resources', false); + $this->fail('Hidden resources should raise an exception when returning an array of matching paths'); + } catch (\RuntimeException $e) { + } + + try { + $kernel->locateResource('@BaseBundle/Resources/hide.txt', __DIR__.'/Fixtures/Resources', true); + $this->fail('Hidden resources should raise an exception when returning the first matching path'); + } catch (\RuntimeException $e) { + } + } + + public function testLocateResourceOnDirectories() + { + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/FooBundle', null, null, 'FooBundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle/', + $kernel->locateResource('@FooBundle/Resources/', __DIR__.'/Fixtures/Resources') + ); + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle', + $kernel->locateResource('@FooBundle/Resources', __DIR__.'/Fixtures/Resources') + ); + + $kernel = $this->getKernel(array('getBundle')); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle', null, null, 'Bundle1Bundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/Resources/', + $kernel->locateResource('@Bundle1Bundle/Resources/') + ); + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/Resources', + $kernel->locateResource('@Bundle1Bundle/Resources') + ); + } + + public function testInitializeBundles() + { + $parent = $this->getBundle(null, null, 'ParentABundle'); + $child = $this->getBundle(null, 'ParentABundle', 'ChildABundle'); + + // use test kernel so we can access getBundleMap() + $kernel = $this->getKernelForTest(array('registerBundles')); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($parent, $child))) + ; + $kernel->boot(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent), $map['ParentABundle']); + } + + public function testInitializeBundlesSupportInheritanceCascade() + { + $grandparent = $this->getBundle(null, null, 'GrandParentBBundle'); + $parent = $this->getBundle(null, 'GrandParentBBundle', 'ParentBBundle'); + $child = $this->getBundle(null, 'ParentBBundle', 'ChildBBundle'); + + // use test kernel so we can access getBundleMap() + $kernel = $this->getKernelForTest(array('registerBundles')); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($grandparent, $parent, $child))) + ; + $kernel->boot(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent, $grandparent), $map['GrandParentBBundle']); + $this->assertEquals(array($child, $parent), $map['ParentBBundle']); + $this->assertEquals(array($child), $map['ChildBBundle']); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Bundle "ChildCBundle" extends bundle "FooBar", which is not registered. + */ + public function testInitializeBundlesThrowsExceptionWhenAParentDoesNotExists() + { + $child = $this->getBundle(null, 'FooBar', 'ChildCBundle'); + $kernel = $this->getKernel(array(), array($child)); + $kernel->boot(); + } + + public function testInitializeBundlesSupportsArbitraryBundleRegistrationOrder() + { + $grandparent = $this->getBundle(null, null, 'GrandParentCBundle'); + $parent = $this->getBundle(null, 'GrandParentCBundle', 'ParentCBundle'); + $child = $this->getBundle(null, 'ParentCBundle', 'ChildCBundle'); + + // use test kernel so we can access getBundleMap() + $kernel = $this->getKernelForTest(array('registerBundles')); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($parent, $grandparent, $child))) + ; + $kernel->boot(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent, $grandparent), $map['GrandParentCBundle']); + $this->assertEquals(array($child, $parent), $map['ParentCBundle']); + $this->assertEquals(array($child), $map['ChildCBundle']); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Bundle "ParentCBundle" is directly extended by two bundles "ChildC2Bundle" and "ChildC1Bundle". + */ + public function testInitializeBundlesThrowsExceptionWhenABundleIsDirectlyExtendedByTwoBundles() + { + $parent = $this->getBundle(null, null, 'ParentCBundle'); + $child1 = $this->getBundle(null, 'ParentCBundle', 'ChildC1Bundle'); + $child2 = $this->getBundle(null, 'ParentCBundle', 'ChildC2Bundle'); + + $kernel = $this->getKernel(array(), array($parent, $child1, $child2)); + $kernel->boot(); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Trying to register two bundles with the same name "DuplicateName" + */ + public function testInitializeBundleThrowsExceptionWhenRegisteringTwoBundlesWithTheSameName() + { + $fooBundle = $this->getBundle(null, null, 'FooBundle', 'DuplicateName'); + $barBundle = $this->getBundle(null, null, 'BarBundle', 'DuplicateName'); + + $kernel = $this->getKernel(array(), array($fooBundle, $barBundle)); + $kernel->boot(); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Bundle "CircularRefBundle" can not extend itself. + */ + public function testInitializeBundleThrowsExceptionWhenABundleExtendsItself() + { + $circularRef = $this->getBundle(null, 'CircularRefBundle', 'CircularRefBundle'); + + $kernel = $this->getKernel(array(), array($circularRef)); + $kernel->boot(); + } + + public function testTerminateReturnsSilentlyIfKernelIsNotBooted() + { + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->never()) + ->method('getHttpKernel'); + + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testTerminateDelegatesTerminationOnlyForTerminableInterface() + { + // does not implement TerminableInterface + $httpKernel = new TestKernel(); + + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->willReturn($httpKernel); + + $kernel->boot(); + $kernel->terminate(Request::create('/'), new Response()); + + $this->assertFalse($httpKernel->terminateCalled, 'terminate() is never called if the kernel class does not implement TerminableInterface'); + + // implements TerminableInterface + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->setMethods(array('terminate')) + ->getMock(); + + $httpKernelMock + ->expects($this->once()) + ->method('terminate'); + + $kernel = $this->getKernel(array('getHttpKernel')); + $kernel->expects($this->exactly(2)) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->boot(); + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testKernelWithoutBundles() + { + $kernel = new KernelWithoutBundles('test', true); + $kernel->boot(); + + $this->assertTrue($kernel->getContainer()->getParameter('test_executed')); + } + + public function testKernelRootDirNameStartingWithANumber() + { + $dir = __DIR__.'/Fixtures/123'; + require_once $dir.'/Kernel123.php'; + $kernel = new \Symfony\Component\HttpKernel\Tests\Fixtures\_123\Kernel123('dev', true); + $this->assertEquals('_123', $kernel->getName()); + } + + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\Kernel::getEnvParameters() method is deprecated as of 3.3 and will be removed in 4.0. Use the %cenv()%c syntax to get the value of any environment variable from configuration files instead. + * @expectedDeprecation The support of special environment variables that start with SYMFONY__ (such as "SYMFONY__FOO__BAR") is deprecated as of 3.3 and will be removed in 4.0. Use the %cenv()%c syntax instead to get the value of environment variables in configuration files. + */ + public function testSymfonyEnvironmentVariables() + { + $_SERVER['SYMFONY__FOO__BAR'] = 'baz'; + + $kernel = $this->getKernel(); + $method = new \ReflectionMethod($kernel, 'getEnvParameters'); + $method->setAccessible(true); + + $envParameters = $method->invoke($kernel); + $this->assertSame('baz', $envParameters['foo.bar']); + + unset($_SERVER['SYMFONY__FOO__BAR']); + } + + public function testProjectDirExtension() + { + $kernel = new CustomProjectDirKernel('test', true); + $kernel->boot(); + + $this->assertSame('foo', $kernel->getProjectDir()); + $this->assertSame('foo', $kernel->getContainer()->getParameter('kernel.project_dir')); + } + + /** + * Returns a mock for the BundleInterface. + * + * @return BundleInterface + */ + protected function getBundle($dir = null, $parent = null, $className = null, $bundleName = null) + { + $bundle = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface') + ->setMethods(array('getPath', 'getParent', 'getName')) + ->disableOriginalConstructor() + ; + + if ($className) { + $bundle->setMockClassName($className); + } + + $bundle = $bundle->getMockForAbstractClass(); + + $bundle + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue(null === $bundleName ? get_class($bundle) : $bundleName)) + ; + + $bundle + ->expects($this->any()) + ->method('getPath') + ->will($this->returnValue($dir)) + ; + + $bundle + ->expects($this->any()) + ->method('getParent') + ->will($this->returnValue($parent)) + ; + + return $bundle; + } + + /** + * Returns a mock for the abstract kernel. + * + * @param array $methods Additional methods to mock (besides the abstract ones) + * @param array $bundles Bundles to register + * + * @return Kernel + */ + protected function getKernel(array $methods = array(), array $bundles = array()) + { + $methods[] = 'registerBundles'; + + $kernel = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Kernel') + ->setMethods($methods) + ->setConstructorArgs(array('test', false)) + ->getMockForAbstractClass() + ; + $kernel->expects($this->any()) + ->method('registerBundles') + ->will($this->returnValue($bundles)) + ; + $p = new \ReflectionProperty($kernel, 'rootDir'); + $p->setAccessible(true); + $p->setValue($kernel, __DIR__.'/Fixtures'); + + return $kernel; + } + + protected function getKernelForTest(array $methods = array()) + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->setConstructorArgs(array('test', false)) + ->setMethods($methods) + ->getMock(); + $p = new \ReflectionProperty($kernel, 'rootDir'); + $p->setAccessible(true); + $p->setValue($kernel, __DIR__.'/Fixtures'); + + return $kernel; + } +} + +class TestKernel implements HttpKernelInterface +{ + public $terminateCalled = false; + + public function terminate() + { + $this->terminateCalled = true; + } + + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + } +} + +class CustomProjectDirKernel extends Kernel +{ + private $baseDir; + + public function __construct() + { + parent::__construct('test', false); + + $this->baseDir = 'foo'; + } + + public function registerBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function getProjectDir() + { + return $this->baseDir; + } + + public function getRootDir() + { + return __DIR__.'/Fixtures'; + } +} diff --git a/vendor/symfony/http-kernel/Tests/Logger.php b/vendor/symfony/http-kernel/Tests/Logger.php new file mode 100644 index 00000000..63c70bf6 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Logger.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Psr\Log\LoggerInterface; + +class Logger implements LoggerInterface +{ + protected $logs; + + public function __construct() + { + $this->clear(); + } + + public function getLogs($level = false) + { + return false === $level ? $this->logs : $this->logs[$level]; + } + + public function clear() + { + $this->logs = array( + 'emergency' => array(), + 'alert' => array(), + 'critical' => array(), + 'error' => array(), + 'warning' => array(), + 'notice' => array(), + 'info' => array(), + 'debug' => array(), + ); + } + + public function log($level, $message, array $context = array()) + { + $this->logs[$level][] = $message; + } + + public function emergency($message, array $context = array()) + { + $this->log('emergency', $message, $context); + } + + public function alert($message, array $context = array()) + { + $this->log('alert', $message, $context); + } + + public function critical($message, array $context = array()) + { + $this->log('critical', $message, $context); + } + + public function error($message, array $context = array()) + { + $this->log('error', $message, $context); + } + + public function warning($message, array $context = array()) + { + $this->log('warning', $message, $context); + } + + public function notice($message, array $context = array()) + { + $this->log('notice', $message, $context); + } + + public function info($message, array $context = array()) + { + $this->log('info', $message, $context); + } + + public function debug($message, array $context = array()) + { + $this->log('debug', $message, $context); + } +} diff --git a/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php b/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php new file mode 100644 index 00000000..99ff2075 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php @@ -0,0 +1,350 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profile; + +class FileProfilerStorageTest extends TestCase +{ + private $tmpDir; + private $storage; + + protected function setUp() + { + $this->tmpDir = sys_get_temp_dir().'/sf2_profiler_file_storage'; + if (is_dir($this->tmpDir)) { + self::cleanDir(); + } + $this->storage = new FileProfilerStorage('file:'.$this->tmpDir); + $this->storage->purge(); + } + + protected function tearDown() + { + self::cleanDir(); + } + + public function testStore() + { + for ($i = 0; $i < 10; ++$i) { + $profile = new Profile('token_'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar'); + $profile->setMethod('GET'); + $this->storage->write($profile); + } + $this->assertCount(10, $this->storage->find('127.0.0.1', 'http://foo.bar', 20, 'GET'), '->write() stores data in the storage'); + } + + public function testChildren() + { + $parentProfile = new Profile('token_parent'); + $parentProfile->setIp('127.0.0.1'); + $parentProfile->setUrl('http://foo.bar/parent'); + + $childProfile = new Profile('token_child'); + $childProfile->setIp('127.0.0.1'); + $childProfile->setUrl('http://foo.bar/child'); + + $parentProfile->addChild($childProfile); + + $this->storage->write($parentProfile); + $this->storage->write($childProfile); + + // Load them from storage + $parentProfile = $this->storage->read('token_parent'); + $childProfile = $this->storage->read('token_child'); + + // Check child has link to parent + $this->assertNotNull($childProfile->getParent()); + $this->assertEquals($parentProfile->getToken(), $childProfile->getParentToken()); + + // Check parent has child + $children = $parentProfile->getChildren(); + $this->assertCount(1, $children); + $this->assertEquals($childProfile->getToken(), $children[0]->getToken()); + } + + public function testStoreSpecialCharsInUrl() + { + // The storage accepts special characters in URLs (Even though URLs are not + // supposed to contain them) + $profile = new Profile('simple_quote'); + $profile->setUrl('http://foo.bar/\''); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('simple_quote'), '->write() accepts single quotes in URL'); + + $profile = new Profile('double_quote'); + $profile->setUrl('http://foo.bar/"'); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('double_quote'), '->write() accepts double quotes in URL'); + + $profile = new Profile('backslash'); + $profile->setUrl('http://foo.bar/\\'); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('backslash'), '->write() accepts backslash in URL'); + + $profile = new Profile('comma'); + $profile->setUrl('http://foo.bar/,'); + $this->storage->write($profile); + $this->assertTrue(false !== $this->storage->read('comma'), '->write() accepts comma in URL'); + } + + public function testStoreDuplicateToken() + { + $profile = new Profile('token'); + $profile->setUrl('http://example.com/'); + + $this->assertTrue($this->storage->write($profile), '->write() returns true when the token is unique'); + + $profile->setUrl('http://example.net/'); + + $this->assertTrue($this->storage->write($profile), '->write() returns true when the token is already present in the storage'); + $this->assertEquals('http://example.net/', $this->storage->read('token')->getUrl(), '->write() overwrites the current profile data'); + + $this->assertCount(1, $this->storage->find('', '', 1000, ''), '->find() does not return the same profile twice'); + } + + public function testRetrieveByIp() + { + $profile = new Profile('token'); + $profile->setIp('127.0.0.1'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertCount(1, $this->storage->find('127.0.0.1', '', 10, 'GET'), '->find() retrieve a record by IP'); + $this->assertCount(0, $this->storage->find('127.0.%.1', '', 10, 'GET'), '->find() does not interpret a "%" as a wildcard in the IP'); + $this->assertCount(0, $this->storage->find('127.0._.1', '', 10, 'GET'), '->find() does not interpret a "_" as a wildcard in the IP'); + } + + public function testRetrieveByStatusCode() + { + $profile200 = new Profile('statuscode200'); + $profile200->setStatusCode(200); + $this->storage->write($profile200); + + $profile404 = new Profile('statuscode404'); + $profile404->setStatusCode(404); + $this->storage->write($profile404); + + $this->assertCount(1, $this->storage->find(null, null, 10, null, null, null, '200'), '->find() retrieve a record by Status code 200'); + $this->assertCount(1, $this->storage->find(null, null, 10, null, null, null, '404'), '->find() retrieve a record by Status code 404'); + } + + public function testRetrieveByUrl() + { + $profile = new Profile('simple_quote'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/\''); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('double_quote'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/"'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('backslash'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo\\bar/'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('percent'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/%'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('underscore'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/_'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $profile = new Profile('semicolon'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/;'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/\'', 10, 'GET'), '->find() accepts single quotes in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/"', 10, 'GET'), '->find() accepts double quotes in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo\\bar/', 10, 'GET'), '->find() accepts backslash in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/;', 10, 'GET'), '->find() accepts semicolon in URLs'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/%', 10, 'GET'), '->find() does not interpret a "%" as a wildcard in the URL'); + $this->assertCount(1, $this->storage->find('127.0.0.1', 'http://foo.bar/_', 10, 'GET'), '->find() does not interpret a "_" as a wildcard in the URL'); + } + + public function testStoreTime() + { + $dt = new \DateTime('now'); + $start = $dt->getTimestamp(); + + for ($i = 0; $i < 3; ++$i) { + $dt->modify('+1 minute'); + $profile = new Profile('time_'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar'); + $profile->setTime($dt->getTimestamp()); + $profile->setMethod('GET'); + $this->storage->write($profile); + } + + $records = $this->storage->find('', '', 3, 'GET', $start, time() + 3 * 60); + $this->assertCount(3, $records, '->find() returns all previously added records'); + $this->assertEquals($records[0]['token'], 'time_2', '->find() returns records ordered by time in descendant order'); + $this->assertEquals($records[1]['token'], 'time_1', '->find() returns records ordered by time in descendant order'); + $this->assertEquals($records[2]['token'], 'time_0', '->find() returns records ordered by time in descendant order'); + + $records = $this->storage->find('', '', 3, 'GET', $start, time() + 2 * 60); + $this->assertCount(2, $records, '->find() should return only first two of the previously added records'); + } + + public function testRetrieveByEmptyUrlAndIp() + { + for ($i = 0; $i < 5; ++$i) { + $profile = new Profile('token_'.$i); + $profile->setMethod('GET'); + $this->storage->write($profile); + } + $this->assertCount(5, $this->storage->find('', '', 10, 'GET'), '->find() returns all previously added records'); + $this->storage->purge(); + } + + public function testRetrieveByMethodAndLimit() + { + foreach (array('POST', 'GET') as $method) { + for ($i = 0; $i < 5; ++$i) { + $profile = new Profile('token_'.$i.$method); + $profile->setMethod($method); + $this->storage->write($profile); + } + } + + $this->assertCount(5, $this->storage->find('', '', 5, 'POST')); + + $this->storage->purge(); + } + + public function testPurge() + { + $profile = new Profile('token1'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.com/'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertTrue(false !== $this->storage->read('token1')); + $this->assertCount(1, $this->storage->find('127.0.0.1', '', 10, 'GET')); + + $profile = new Profile('token2'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.net/'); + $profile->setMethod('GET'); + $this->storage->write($profile); + + $this->assertTrue(false !== $this->storage->read('token2')); + $this->assertCount(2, $this->storage->find('127.0.0.1', '', 10, 'GET')); + + $this->storage->purge(); + + $this->assertEmpty($this->storage->read('token'), '->purge() removes all data stored by profiler'); + $this->assertCount(0, $this->storage->find('127.0.0.1', '', 10, 'GET'), '->purge() removes all items from index'); + } + + public function testDuplicates() + { + for ($i = 1; $i <= 5; ++$i) { + $profile = new Profile('foo'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.net/'); + $profile->setMethod('GET'); + + ///three duplicates + $this->storage->write($profile); + $this->storage->write($profile); + $this->storage->write($profile); + } + $this->assertCount(3, $this->storage->find('127.0.0.1', 'http://example.net/', 3, 'GET'), '->find() method returns incorrect number of entries'); + } + + public function testStatusCode() + { + $profile = new Profile('token1'); + $profile->setStatusCode(200); + $this->storage->write($profile); + + $profile = new Profile('token2'); + $profile->setStatusCode(404); + $this->storage->write($profile); + + $tokens = $this->storage->find('', '', 10, ''); + $this->assertCount(2, $tokens); + $this->assertContains($tokens[0]['status_code'], array(200, 404)); + $this->assertContains($tokens[1]['status_code'], array(200, 404)); + } + + public function testMultiRowIndexFile() + { + $iteration = 3; + for ($i = 0; $i < $iteration; ++$i) { + $profile = new Profile('token'.$i); + $profile->setIp('127.0.0.'.$i); + $profile->setUrl('http://foo.bar/'.$i); + + $this->storage->write($profile); + $this->storage->write($profile); + $this->storage->write($profile); + } + + $handle = fopen($this->tmpDir.'/index.csv', 'r'); + for ($i = 0; $i < $iteration; ++$i) { + $row = fgetcsv($handle); + $this->assertEquals('token'.$i, $row[0]); + $this->assertEquals('127.0.0.'.$i, $row[1]); + $this->assertEquals('http://foo.bar/'.$i, $row[3]); + } + $this->assertFalse(fgetcsv($handle)); + } + + public function testReadLineFromFile() + { + $r = new \ReflectionMethod($this->storage, 'readLineFromFile'); + + $r->setAccessible(true); + + $h = tmpfile(); + + fwrite($h, "line1\n\n\nline2\n"); + fseek($h, 0, SEEK_END); + + $this->assertEquals('line2', $r->invoke($this->storage, $h)); + $this->assertEquals('line1', $r->invoke($this->storage, $h)); + } + + protected function cleanDir() + { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($this->tmpDir, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php b/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.php new file mode 100644 index 00000000..1a6f5463 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.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\HttpKernel\Tests\Profiler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ProfilerTest extends TestCase +{ + private $tmp; + private $storage; + + public function testCollect() + { + $request = new Request(); + $request->query->set('foo', 'bar'); + $response = new Response('', 204); + $collector = new RequestDataCollector(); + + $profiler = new Profiler($this->storage); + $profiler->add($collector); + $profile = $profiler->collect($request, $response); + $profiler->saveProfile($profile); + + $this->assertSame(204, $profile->getStatusCode()); + $this->assertSame('GET', $profile->getMethod()); + $this->assertSame('bar', $profile->getCollector('request')->getRequestQuery()->all()['foo']->getValue()); + } + + public function testFindWorksWithDates() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, '7th April 2014', '9th April 2014')); + } + + public function testFindWorksWithTimestamps() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, '1396828800', '1397001600')); + } + + public function testFindWorksWithInvalidDates() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, 'some string', '')); + } + + public function testFindWorksWithStatusCode() + { + $profiler = new Profiler($this->storage); + + $this->assertCount(0, $profiler->find(null, null, null, null, null, null, '204')); + } + + protected function setUp() + { + $this->tmp = tempnam(sys_get_temp_dir(), 'sf2_profiler'); + if (file_exists($this->tmp)) { + @unlink($this->tmp); + } + + $this->storage = new FileProfilerStorage('file:'.$this->tmp); + $this->storage->purge(); + } + + protected function tearDown() + { + if (null !== $this->storage) { + $this->storage->purge(); + $this->storage = null; + + @unlink($this->tmp); + } + } +} diff --git a/vendor/symfony/http-kernel/Tests/TestHttpKernel.php b/vendor/symfony/http-kernel/Tests/TestHttpKernel.php new file mode 100644 index 00000000..3ec59272 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/TestHttpKernel.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestHttpKernel extends HttpKernel implements ControllerResolverInterface, ArgumentResolverInterface +{ + public function __construct() + { + parent::__construct(new EventDispatcher(), $this, null, $this); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + return new Response('Request: '.$request->getRequestUri()); + } +} diff --git a/vendor/symfony/http-kernel/Tests/UriSignerTest.php b/vendor/symfony/http-kernel/Tests/UriSignerTest.php new file mode 100644 index 00000000..253a4c91 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/UriSignerTest.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\HttpKernel\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\UriSigner; + +class UriSignerTest extends TestCase +{ + public function testSign() + { + $signer = new UriSigner('foobar'); + + $this->assertContains('?_hash=', $signer->sign('http://example.com/foo')); + $this->assertContains('&_hash=', $signer->sign('http://example.com/foo?foo=bar')); + } + + public function testCheck() + { + $signer = new UriSigner('foobar'); + + $this->assertFalse($signer->check('http://example.com/foo?_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo&bar=foo')); + + $this->assertTrue($signer->check($signer->sign('http://example.com/foo'))); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar'))); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&0=integer'))); + + $this->assertTrue($signer->sign('http://example.com/foo?foo=bar&bar=foo') === $signer->sign('http://example.com/foo?bar=foo&foo=bar')); + } + + public function testCheckWithDifferentArgSeparator() + { + $this->iniSet('arg_separator.output', '&'); + $signer = new UriSigner('foobar'); + + $this->assertSame( + 'http://example.com/foo?baz=bay&foo=bar&_hash=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D', + $signer->sign('http://example.com/foo?foo=bar&baz=bay') + ); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay'))); + } + + public function testCheckWithDifferentParameter() + { + $signer = new UriSigner('foobar', 'qux'); + + $this->assertSame( + 'http://example.com/foo?baz=bay&foo=bar&qux=rIOcC%2FF3DoEGo%2FvnESjSp7uU9zA9S%2F%2BOLhxgMexoPUM%3D', + $signer->sign('http://example.com/foo?foo=bar&baz=bay') + ); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar&baz=bay'))); + } +} diff --git a/vendor/symfony/http-kernel/UriSigner.php b/vendor/symfony/http-kernel/UriSigner.php new file mode 100644 index 00000000..6f4f8865 --- /dev/null +++ b/vendor/symfony/http-kernel/UriSigner.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Signs URIs. + * + * @author Fabien Potencier + */ +class UriSigner +{ + private $secret; + private $parameter; + + /** + * Constructor. + * + * @param string $secret A secret + * @param string $parameter Query string parameter to use + */ + public function __construct($secret, $parameter = '_hash') + { + $this->secret = $secret; + $this->parameter = $parameter; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding the query string parameter + * which value depends on the URI and the secret. + * + * @param string $uri A URI to sign + * + * @return string The signed URI + */ + public function sign($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + $uri = $this->buildUrl($url, $params); + + return $uri.(false === strpos($uri, '?') ? '?' : '&').$this->parameter.'='.$this->computeHash($uri); + } + + /** + * Checks that a URI contains the correct hash. + * + * The query string parameter must be the last one + * (as it is generated that way by the sign() method, it should + * never be a problem). + * + * @param string $uri A signed URI + * + * @return bool True if the URI is signed correctly, false otherwise + */ + public function check($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + if (empty($params[$this->parameter])) { + return false; + } + + $hash = urlencode($params[$this->parameter]); + unset($params[$this->parameter]); + + return $this->computeHash($this->buildUrl($url, $params)) === $hash; + } + + private function computeHash($uri) + { + return urlencode(base64_encode(hash_hmac('sha256', $uri, $this->secret, true))); + } + + private function buildUrl(array $url, array $params = array()) + { + ksort($params, SORT_STRING); + $url['query'] = http_build_query($params, '', '&'); + + $scheme = isset($url['scheme']) ? $url['scheme'].'://' : ''; + $host = isset($url['host']) ? $url['host'] : ''; + $port = isset($url['port']) ? ':'.$url['port'] : ''; + $user = isset($url['user']) ? $url['user'] : ''; + $pass = isset($url['pass']) ? ':'.$url['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = isset($url['path']) ? $url['path'] : ''; + $query = isset($url['query']) && $url['query'] ? '?'.$url['query'] : ''; + $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : ''; + + return $scheme.$user.$pass.$host.$port.$path.$query.$fragment; + } +} diff --git a/vendor/symfony/http-kernel/composer.json b/vendor/symfony/http-kernel/composer.json new file mode 100644 index 00000000..babc8663 --- /dev/null +++ b/vendor/symfony/http-kernel/composer.json @@ -0,0 +1,70 @@ +{ + "name": "symfony/http-kernel", + "type": "library", + "description": "Symfony HttpKernel Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/http-foundation": "~3.3", + "symfony/debug": "~2.8|~3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "symfony/browser-kit": "~2.8|~3.0", + "symfony/class-loader": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/css-selector": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "symfony/dom-crawler": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/finder": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0", + "symfony/routing": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "symfony/templating": "~2.8|~3.0", + "symfony/translation": "~2.8|~3.0", + "symfony/var-dumper": "~3.3", + "psr/cache": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3", + "twig/twig": "<1.34|<2.4,>=2" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpKernel\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/http-kernel/phpunit.xml.dist b/vendor/symfony/http-kernel/phpunit.xml.dist new file mode 100644 index 00000000..e0de769d --- /dev/null +++ b/vendor/symfony/http-kernel/phpunit.xml.dist @@ -0,0 +1,40 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + + + + + + Symfony\Component\HttpFoundation + + + + + diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 00000000..39fa189d --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 00000000..97e8c9b4 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,664 @@ + + * + * 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_convert_case - Perform case folding on a string + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within anothers + * - 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_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + const MB_CASE_FOLD = PHP_INT_MAX; + + private static $encodingList = array('ASCII', 'UTF-8'); + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = array( + array('µ','ſ',"\xCD\x85",'ς',"\xCF\x90","\xCF\x91","\xCF\x95","\xCF\x96","\xCF\xB0","\xCF\xB1","\xCF\xB5","\xE1\xBA\x9B","\xE1\xBE\xBE"), + array('μ','s','ι', 'σ','β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1",'ι'), + ); + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) + { + $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); + + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + 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) { + $s = preg_replace_callback('/\b\p{Ll}/u', array(__CLASS__, 'title_case_upper'), $s); + $s = preg_replace_callback('/\B[\p{Lu}\p{Lt}]+/u', array(__CLASS__, 'title_case_lower'), $s); + } else { + if (MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $i = 0; + $len = strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { + self::$internalEncoding = $encoding; + + return true; + } + + return false; + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($lang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $lang; + + return true; + } + + return false; + } + + public static function mb_list_encodings() + { + return array('UTF-8'); + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return array('utf8'); + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + 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); + } + + if ('' === $needle .= '') { + trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + + return false; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (0 === strcasecmp($c, 'none')) { + return true; + } + + return null !== $c ? false : 'none'; + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return iconv_substr($s, $start, $length, $encoding).''; + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrchr($haystack, $needle, $part); + } + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = array( + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ); + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = chr($code); + } elseif (0x800 > $code) { + $s = chr(0xC0 | $code >> 6).chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = chr(0xE0 | $code >> 12).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F); + } else { + $s = chr(0xF0 | $code >> 18).chr(0x80 | $code >> 12 & 0x3F).chr(0x80 | $code >> 6 & 0x3F).chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + $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($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_lower($s) + { + return self::mb_convert_case($s[0], MB_CASE_LOWER, 'UTF-8'); + } + + private static function title_case_upper($s) + { + return self::mb_convert_case($s[0], MB_CASE_UPPER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 00000000..342e8286 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](http://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 00000000..3ca16416 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1101 @@ + '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', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', +); + +$result =& $data; +unset($data); + +return $result; diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php new file mode 100644 index 00000000..ec942212 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php @@ -0,0 +1,1109 @@ + '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', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', +); + +$result =& $data; +unset($data); + +return $result; diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 00000000..33722910 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_strlen')) { + define('MB_CASE_UPPER', 0); + define('MB_CASE_LOWER', 1); + define('MB_CASE_TITLE', 2); + + function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); } + function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); } + function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); } + function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); } + function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); } + function mb_language($lang = null) { return p\Mbstring::mb_language($lang); } + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } + function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); } + function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); } + function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); } + function mb_parse_str($s, &$result = array()) { parse_str($s, $result); } + function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); } + function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); } + function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); } + function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); } + function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); } + function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); } + function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); } + function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); } + function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); } + function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); } + function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); } + function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); } + function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); } + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } + function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); } + function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); } + function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); } + function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); } + function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } + function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } +} +if (!function_exists('mb_chr')) { + function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); } + function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); } + function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); } +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 00000000..48fc3ddf --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + } +} diff --git a/vendor/symfony/routing/.gitignore b/vendor/symfony/routing/.gitignore new file mode 100644 index 00000000..c49a5d8d --- /dev/null +++ b/vendor/symfony/routing/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/routing/Annotation/Route.php b/vendor/symfony/routing/Annotation/Route.php new file mode 100644 index 00000000..e3d51570 --- /dev/null +++ b/vendor/symfony/routing/Annotation/Route.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Annotation; + +/** + * Annotation class for @Route(). + * + * @Annotation + * @Target({"CLASS", "METHOD"}) + * + * @author Fabien Potencier + */ +class Route +{ + private $path; + private $name; + private $requirements = array(); + private $options = array(); + private $defaults = array(); + private $host; + private $methods = array(); + private $schemes = array(); + private $condition; + + /** + * Constructor. + * + * @param array $data An array of key/value parameters + * + * @throws \BadMethodCallException + */ + public function __construct(array $data) + { + if (isset($data['value'])) { + $data['path'] = $data['value']; + unset($data['value']); + } + + foreach ($data as $key => $value) { + $method = 'set'.str_replace('_', '', $key); + if (!method_exists($this, $method)) { + throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, get_class($this))); + } + $this->$method($value); + } + } + + public function setPath($path) + { + $this->path = $path; + } + + public function getPath() + { + return $this->path; + } + + public function setHost($pattern) + { + $this->host = $pattern; + } + + public function getHost() + { + return $this->host; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function setRequirements($requirements) + { + $this->requirements = $requirements; + } + + public function getRequirements() + { + return $this->requirements; + } + + public function setOptions($options) + { + $this->options = $options; + } + + public function getOptions() + { + return $this->options; + } + + public function setDefaults($defaults) + { + $this->defaults = $defaults; + } + + public function getDefaults() + { + return $this->defaults; + } + + public function setSchemes($schemes) + { + $this->schemes = is_array($schemes) ? $schemes : array($schemes); + } + + public function getSchemes() + { + return $this->schemes; + } + + public function setMethods($methods) + { + $this->methods = is_array($methods) ? $methods : array($methods); + } + + public function getMethods() + { + return $this->methods; + } + + public function setCondition($condition) + { + $this->condition = $condition; + } + + public function getCondition() + { + return $this->condition; + } +} diff --git a/vendor/symfony/routing/CHANGELOG.md b/vendor/symfony/routing/CHANGELOG.md new file mode 100644 index 00000000..d04581f4 --- /dev/null +++ b/vendor/symfony/routing/CHANGELOG.md @@ -0,0 +1,219 @@ +CHANGELOG +========= + +3.3.0 +----- + + * [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0. + * router.options.generator_class + * router.options.generator_base_class + * router.options.generator_dumper_class + * router.options.matcher_class + * router.options.matcher_base_class + * router.options.matcher_dumper_class + * router.options.matcher.cache_class + * router.options.generator.cache_class + +3.2.0 +----- + + * Added support for `bool`, `int`, `float`, `string`, `list` and `map` defaults in XML configurations. + * Added support for UTF-8 requirements + +2.8.0 +----- + + * allowed specifying a directory to recursively load all routing configuration files it contains + * Added ObjectRouteLoader and ServiceRouteLoader that allow routes to be loaded + by calling a method on an object/service. + * [DEPRECATION] Deprecated the hardcoded value for the `$referenceType` argument of the `UrlGeneratorInterface::generate` method. + Use the constants defined in the `UrlGeneratorInterface` instead. + + Before: + + ```php + $router->generate('blog_show', array('slug' => 'my-blog-post'), true); + ``` + + After: + + ```php + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + $router->generate('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + ``` + +2.5.0 +----- + + * [DEPRECATION] The `ApacheMatcherDumper` and `ApacheUrlMatcher` were deprecated and + will be removed in Symfony 3.0, since the performance gains were minimal and + it's hard to replicate the behaviour of PHP implementation. + +2.3.0 +----- + + * added RequestContext::getQueryString() + +2.2.0 +----- + + * [DEPRECATION] Several route settings have been renamed (the old ones will be removed in 3.0): + + * The `pattern` setting for a route has been deprecated in favor of `path` + * The `_scheme` and `_method` requirements have been moved to the `schemes` and `methods` settings + + Before: + + ```yaml + article_edit: + pattern: /article/{id} + requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' } + ``` + + ```xml + + POST|PUT + https + \d+ + + ``` + + ```php + $route = new Route(); + $route->setPattern('/article/{id}'); + $route->setRequirement('_method', 'POST|PUT'); + $route->setRequirement('_scheme', 'https'); + ``` + + After: + + ```yaml + article_edit: + path: /article/{id} + methods: [POST, PUT] + schemes: https + requirements: { 'id': '\d+' } + ``` + + ```xml + + \d+ + + ``` + + ```php + $route = new Route(); + $route->setPath('/article/{id}'); + $route->setMethods(array('POST', 'PUT')); + $route->setSchemes('https'); + ``` + + * [BC BREAK] RouteCollection does not behave like a tree structure anymore but as + a flat array of Routes. So when using PHP to build the RouteCollection, you must + make sure to add routes to the sub-collection before adding it to the parent + collection (this is not relevant when using YAML or XML for Route definitions). + + Before: + + ```php + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $rootCollection->addCollection($subCollection); + $subCollection->add('foo', new Route('/foo')); + ``` + + After: + + ```php + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $subCollection->add('foo', new Route('/foo')); + $rootCollection->addCollection($subCollection); + ``` + + Also one must call `addCollection` from the bottom to the top hierarchy. + So the correct sequence is the following (and not the reverse): + + ```php + $childCollection->addCollection($grandchildCollection); + $rootCollection->addCollection($childCollection); + ``` + + * [DEPRECATION] The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()` + have been deprecated and will be removed in Symfony 2.3. + * [BC BREAK] Misusing the `RouteCollection::addPrefix` method to add defaults, requirements + or options without adding a prefix is not supported anymore. So if you called `addPrefix` + with an empty prefix or `/` only (both have no relevance), like + `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)` + you need to use the new dedicated methods `addDefaults($defaultsArray)`, + `addRequirements($requirementsArray)` or `addOptions($optionsArray)` instead. + * [DEPRECATION] The `$options` parameter to `RouteCollection::addPrefix()` has been deprecated + because adding options has nothing to do with adding a path prefix. If you want to add options + to all child routes of a RouteCollection, you can use `addOptions()`. + * [DEPRECATION] The method `RouteCollection::getPrefix()` has been deprecated + because it suggested that all routes in the collection would have this prefix, which is + not necessarily true. On top of that, since there is no tree structure anymore, this method + is also useless. Don't worry about performance, prefix optimization for matching is still done + in the dumper, which was also improved in 2.2.0 to find even more grouping possibilities. + * [DEPRECATION] `RouteCollection::addCollection(RouteCollection $collection)` should now only be + used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options` + will still work, but have been deprecated. The `addPrefix` method should be used for this + use-case instead. + Before: `$parentCollection->addCollection($collection, '/prefix', array(...), array(...))` + After: + ```php + $collection->addPrefix('/prefix', array(...), array(...)); + $parentCollection->addCollection($collection); + ``` + * added support for the method default argument values when defining a @Route + * Adjacent placeholders without separator work now, e.g. `/{x}{y}{z}.{_format}`. + * Characters that function as separator between placeholders are now whitelisted + to fix routes with normal text around a variable, e.g. `/prefix{var}suffix`. + * [BC BREAK] The default requirement of a variable has been changed slightly. + Previously it disallowed the previous and the next char around a variable. Now + it disallows the slash (`/`) and the next char. Using the previous char added + no value and was problematic because the route `/index.{_format}` would be + matched by `/index.ht/ml`. + * The default requirement now uses possessive quantifiers when possible which + improves matching performance by up to 20% because it prevents backtracking + when it's not needed. + * The ConfigurableRequirementsInterface can now also be used to disable the requirements + check on URL generation completely by calling `setStrictRequirements(null)`. It + improves performance in production environment as you should know that params always + pass the requirements (otherwise it would break your link anyway). + * There is no restriction on the route name anymore. So non-alphanumeric characters + are now also allowed. + * [BC BREAK] `RouteCompilerInterface::compile(Route $route)` was made static + (only relevant if you implemented your own RouteCompiler). + * Added possibility to generate relative paths and network paths in the UrlGenerator, e.g. + "../parent-file" and "//example.com/dir/file". The third parameter in + `UrlGeneratorInterface::generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)` + now accepts more values and you should use the constants defined in `UrlGeneratorInterface` for + claritiy. The old method calls with a Boolean parameter will continue to work because they + equal the signature using the constants. + +2.1.0 +----- + + * added RequestMatcherInterface + * added RequestContext::fromRequest() + * the UrlMatcher does not throw a \LogicException anymore when the required + scheme is not the current one + * added TraceableUrlMatcher + * added the possibility to define options, default values and requirements + for placeholders in prefix, including imported routes + * added RouterInterface::getRouteCollection + * [BC BREAK] the UrlMatcher urldecodes the route parameters only once, they + were decoded twice before. Note that the `urldecode()` calls have been + changed for a single `rawurldecode()` in order to support `+` for input + paths. + * added RouteCollection::getRoot method to retrieve the root of a + RouteCollection tree + * [BC BREAK] made RouteCollection::setParent private which could not have + been used anyway without creating inconsistencies + * [BC BREAK] RouteCollection::remove also removes a route from parent + collections (not only from its children) + * added ConfigurableRequirementsInterface that allows to disable exceptions + (and generate empty URLs instead) when generating a route with an invalid + parameter value diff --git a/vendor/symfony/routing/CompiledRoute.php b/vendor/symfony/routing/CompiledRoute.php new file mode 100644 index 00000000..f52e3aa0 --- /dev/null +++ b/vendor/symfony/routing/CompiledRoute.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * CompiledRoutes are returned by the RouteCompiler class. + * + * @author Fabien Potencier + */ +class CompiledRoute implements \Serializable +{ + private $variables; + private $tokens; + private $staticPrefix; + private $regex; + private $pathVariables; + private $hostVariables; + private $hostRegex; + private $hostTokens; + + /** + * Constructor. + * + * @param string $staticPrefix The static prefix of the compiled route + * @param string $regex The regular expression to use to match this route + * @param array $tokens An array of tokens to use to generate URL for this route + * @param array $pathVariables An array of path variables + * @param string|null $hostRegex Host regex + * @param array $hostTokens Host tokens + * @param array $hostVariables An array of host variables + * @param array $variables An array of variables (variables defined in the path and in the host patterns) + */ + public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) + { + $this->staticPrefix = (string) $staticPrefix; + $this->regex = $regex; + $this->tokens = $tokens; + $this->pathVariables = $pathVariables; + $this->hostRegex = $hostRegex; + $this->hostTokens = $hostTokens; + $this->hostVariables = $hostVariables; + $this->variables = $variables; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + 'vars' => $this->variables, + 'path_prefix' => $this->staticPrefix, + 'path_regex' => $this->regex, + 'path_tokens' => $this->tokens, + 'path_vars' => $this->pathVariables, + 'host_regex' => $this->hostRegex, + 'host_tokens' => $this->hostTokens, + 'host_vars' => $this->hostVariables, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $data = unserialize($serialized, array('allowed_classes' => false)); + } else { + $data = unserialize($serialized); + } + + $this->variables = $data['vars']; + $this->staticPrefix = $data['path_prefix']; + $this->regex = $data['path_regex']; + $this->tokens = $data['path_tokens']; + $this->pathVariables = $data['path_vars']; + $this->hostRegex = $data['host_regex']; + $this->hostTokens = $data['host_tokens']; + $this->hostVariables = $data['host_vars']; + } + + /** + * Returns the static prefix. + * + * @return string The static prefix + */ + public function getStaticPrefix() + { + return $this->staticPrefix; + } + + /** + * Returns the regex. + * + * @return string The regex + */ + public function getRegex() + { + return $this->regex; + } + + /** + * Returns the host regex. + * + * @return string|null The host regex or null + */ + public function getHostRegex() + { + return $this->hostRegex; + } + + /** + * Returns the tokens. + * + * @return array The tokens + */ + public function getTokens() + { + return $this->tokens; + } + + /** + * Returns the host tokens. + * + * @return array The tokens + */ + public function getHostTokens() + { + return $this->hostTokens; + } + + /** + * Returns the variables. + * + * @return array The variables + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Returns the path variables. + * + * @return array The variables + */ + public function getPathVariables() + { + return $this->pathVariables; + } + + /** + * Returns the host variables. + * + * @return array The variables + */ + public function getHostVariables() + { + return $this->hostVariables; + } +} diff --git a/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php b/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php new file mode 100644 index 00000000..73a8f851 --- /dev/null +++ b/vendor/symfony/routing/DependencyInjection/RoutingResolverPass.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\DependencyInjection; + +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * Adds tagged routing.loader services to routing.resolver service. + * + * @author Fabien Potencier + */ +class RoutingResolverPass implements CompilerPassInterface +{ + private $resolverServiceId; + private $loaderTag; + + public function __construct($resolverServiceId = 'routing.resolver', $loaderTag = 'routing.loader') + { + $this->resolverServiceId = $resolverServiceId; + $this->loaderTag = $loaderTag; + } + + public function process(ContainerBuilder $container) + { + if (false === $container->hasDefinition($this->resolverServiceId)) { + return; + } + + $definition = $container->getDefinition($this->resolverServiceId); + + foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) { + $definition->addMethodCall('addLoader', array(new Reference($id))); + } + } +} diff --git a/vendor/symfony/routing/Exception/ExceptionInterface.php b/vendor/symfony/routing/Exception/ExceptionInterface.php new file mode 100644 index 00000000..db763621 --- /dev/null +++ b/vendor/symfony/routing/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * ExceptionInterface. + * + * @author Alexandre Salomé + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/InvalidParameterException.php b/vendor/symfony/routing/Exception/InvalidParameterException.php new file mode 100644 index 00000000..94d841f4 --- /dev/null +++ b/vendor/symfony/routing/Exception/InvalidParameterException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a parameter is not valid. + * + * @author Alexandre Salomé + */ +class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/MethodNotAllowedException.php b/vendor/symfony/routing/Exception/MethodNotAllowedException.php new file mode 100644 index 00000000..f684c749 --- /dev/null +++ b/vendor/symfony/routing/Exception/MethodNotAllowedException.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was found but the request method is not allowed. + * + * This exception should trigger an HTTP 405 response in your application code. + * + * @author Kris Wallsmith + */ +class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface +{ + /** + * @var array + */ + protected $allowedMethods = array(); + + public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null) + { + $this->allowedMethods = array_map('strtoupper', $allowedMethods); + + parent::__construct($message, $code, $previous); + } + + /** + * Gets the allowed HTTP methods. + * + * @return array + */ + public function getAllowedMethods() + { + return $this->allowedMethods; + } +} diff --git a/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php b/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php new file mode 100644 index 00000000..57f3a40d --- /dev/null +++ b/vendor/symfony/routing/Exception/MissingMandatoryParametersException.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\Routing\Exception; + +/** + * Exception thrown when a route cannot be generated because of missing + * mandatory parameters. + * + * @author Alexandre Salomé + */ +class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/ResourceNotFoundException.php b/vendor/symfony/routing/Exception/ResourceNotFoundException.php new file mode 100644 index 00000000..ccbca152 --- /dev/null +++ b/vendor/symfony/routing/Exception/ResourceNotFoundException.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\Routing\Exception; + +/** + * The resource was not found. + * + * This exception should trigger an HTTP 404 response in your application code. + * + * @author Kris Wallsmith + */ +class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/RouteNotFoundException.php b/vendor/symfony/routing/Exception/RouteNotFoundException.php new file mode 100644 index 00000000..24ab0b44 --- /dev/null +++ b/vendor/symfony/routing/Exception/RouteNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route does not exist. + * + * @author Alexandre Salomé + */ +class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php b/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php new file mode 100644 index 00000000..dc97b7e7 --- /dev/null +++ b/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +/** + * ConfigurableRequirementsInterface must be implemented by URL generators that + * can be configured whether an exception should be generated when the parameters + * do not match the requirements. It is also possible to disable the requirements + * check for URL generation completely. + * + * The possible configurations and use-cases: + * - setStrictRequirements(true): Throw an exception for mismatching requirements. This + * is mostly useful in development environment. + * - setStrictRequirements(false): Don't throw an exception but return null as URL for + * mismatching requirements and log the problem. Useful when you cannot control all + * params because they come from third party libs but don't want to have a 404 in + * production environment. It should log the mismatch so one can review it. + * - setStrictRequirements(null): Return the URL with the given parameters without + * checking the requirements at all. When generating a URL you should either trust + * your params or you validated them beforehand because otherwise it would break your + * link anyway. So in production environment you should know that params always pass + * the requirements. Thus this option allows to disable the check on URL generation for + * performance reasons (saving a preg_match for each requirement every time a URL is + * generated). + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface ConfigurableRequirementsInterface +{ + /** + * Enables or disables the exception on incorrect parameters. + * Passing null will deactivate the requirements check completely. + * + * @param bool|null $enabled + */ + public function setStrictRequirements($enabled); + + /** + * Returns whether to throw an exception on incorrect parameters. + * Null means the requirements check is deactivated completely. + * + * @return bool|null + */ + public function isStrictRequirements(); +} diff --git a/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php b/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php new file mode 100644 index 00000000..4739bd83 --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumper is the base class for all built-in generator dumpers. + * + * @author Fabien Potencier + */ +abstract class GeneratorDumper implements GeneratorDumperInterface +{ + /** + * @var RouteCollection + */ + private $routes; + + /** + * Constructor. + * + * @param RouteCollection $routes The RouteCollection to dump + */ + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php b/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php new file mode 100644 index 00000000..fed34723 --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumperInterface is the interface that all generator dumper classes must implement. + * + * @author Fabien Potencier + */ +interface GeneratorDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to generate a URL of such a route. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php b/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php new file mode 100644 index 00000000..9fd35ea1 --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +/** + * PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class PhpGeneratorDumper extends GeneratorDumper +{ + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the generator class + */ + public function dump(array $options = array()) + { + $options = array_merge(array( + 'class' => 'ProjectUrlGenerator', + 'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + ), $options); + + return <<context = \$context; + \$this->logger = \$logger; + if (null === self::\$declaredRoutes) { + self::\$declaredRoutes = {$this->generateDeclaredRoutes()}; + } + } + +{$this->generateGenerateMethod()} +} + +EOF; + } + + /** + * Generates PHP code representing an array of defined routes + * together with the routes properties (e.g. requirements). + * + * @return string PHP code + */ + private function generateDeclaredRoutes() + { + $routes = "array(\n"; + foreach ($this->getRoutes()->all() as $name => $route) { + $compiledRoute = $route->compile(); + + $properties = array(); + $properties[] = $compiledRoute->getVariables(); + $properties[] = $route->getDefaults(); + $properties[] = $route->getRequirements(); + $properties[] = $compiledRoute->getTokens(); + $properties[] = $compiledRoute->getHostTokens(); + $properties[] = $route->getSchemes(); + + $routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true))); + } + $routes .= ' )'; + + return $routes; + } + + /** + * Generates PHP code representing the `generate` method that implements the UrlGeneratorInterface. + * + * @return string PHP code + */ + private function generateGenerateMethod() + { + return <<<'EOF' + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + if (!isset(self::$declaredRoutes[$name])) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name]; + + return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); + } +EOF; + } +} diff --git a/vendor/symfony/routing/Generator/UrlGenerator.php b/vendor/symfony/routing/Generator/UrlGenerator.php new file mode 100644 index 00000000..342161e1 --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGenerator.php @@ -0,0 +1,339 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Psr\Log\LoggerInterface; + +/** + * UrlGenerator can generate a URL or a path for any route in the RouteCollection + * based on the passed parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface +{ + /** + * @var RouteCollection + */ + protected $routes; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var bool|null + */ + protected $strictRequirements = true; + + /** + * @var LoggerInterface|null + */ + protected $logger; + + /** + * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. + * + * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars + * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g. + * "?" and "#" (would be interpreted wrongly as query and fragment identifier), + * "'" and """ (are used as delimiters in HTML). + */ + protected $decodedChars = array( + // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning + // some webservers don't allow the slash in encoded form in the path for security reasons anyway + // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss + '%2F' => '/', + // the following chars are general delimiters in the URI specification but have only special meaning in the authority component + // so they can safely be used in the path in unencoded form + '%40' => '@', + '%3A' => ':', + // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally + // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability + '%3B' => ';', + '%2C' => ',', + '%3D' => '=', + '%2B' => '+', + '%21' => '!', + '%2A' => '*', + '%7C' => '|', + ); + + /** + * Constructor. + * + * @param RouteCollection $routes A RouteCollection instance + * @param RequestContext $context The context + * @param LoggerInterface|null $logger A logger instance + */ + public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null) + { + $this->routes = $routes; + $this->context = $context; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function setStrictRequirements($enabled) + { + $this->strictRequirements = null === $enabled ? null : (bool) $enabled; + } + + /** + * {@inheritdoc} + */ + public function isStrictRequirements() + { + return $this->strictRequirements; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + if (null === $route = $this->routes->get($name)) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + // the Route has a cache of its own and is not recompiled as long as it does not get modified + $compiledRoute = $route->compile(); + + return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); + } + + /** + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = array()) + { + $variables = array_flip($variables); + $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); + + // all params must be given + if ($diff = array_diff_key($variables, $mergedParams)) { + throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name)); + } + + $url = ''; + $optional = true; + $message = 'Parameter "{parameter}" for route "{route}" must match "{expected}" ("{given}" given) to generate a corresponding URL.'; + foreach ($tokens as $token) { + if ('variable' === $token[0]) { + if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) { + // check requirement + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) { + if ($this->strictRequirements) { + throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]))); + } + + if ($this->logger) { + $this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]])); + } + + return; + } + + $url = $token[1].$mergedParams[$token[3]].$url; + $optional = false; + } + } else { + // static text + $url = $token[1].$url; + $optional = false; + } + } + + if ('' === $url) { + $url = '/'; + } + + // the contexts base URL is already encoded (see Symfony\Component\HttpFoundation\Request) + $url = strtr(rawurlencode($url), $this->decodedChars); + + // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3 + // so we need to encode them as they are not used for this purpose here + // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route + $url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/')); + if ('/..' === substr($url, -3)) { + $url = substr($url, 0, -2).'%2E%2E'; + } elseif ('/.' === substr($url, -2)) { + $url = substr($url, 0, -1).'%2E'; + } + + $schemeAuthority = ''; + if ($host = $this->context->getHost()) { + $scheme = $this->context->getScheme(); + + if ($requiredSchemes) { + if (!in_array($scheme, $requiredSchemes, true)) { + $referenceType = self::ABSOLUTE_URL; + $scheme = current($requiredSchemes); + } + } + + if ($hostTokens) { + $routeHost = ''; + foreach ($hostTokens as $token) { + if ('variable' === $token[0]) { + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i'.(empty($token[4]) ? '' : 'u'), $mergedParams[$token[3]])) { + if ($this->strictRequirements) { + throw new InvalidParameterException(strtr($message, array('{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]))); + } + + if ($this->logger) { + $this->logger->error($message, array('parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]])); + } + + return; + } + + $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; + } else { + $routeHost = $token[1].$routeHost; + } + } + + if ($routeHost !== $host) { + $host = $routeHost; + if (self::ABSOLUTE_URL !== $referenceType) { + $referenceType = self::NETWORK_PATH; + } + } + } + + if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) { + $port = ''; + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://"; + $schemeAuthority .= $host.$port; + } + } + + if (self::RELATIVE_PATH === $referenceType) { + $url = self::getRelativePath($this->context->getPathInfo(), $url); + } else { + $url = $schemeAuthority.$this->context->getBaseUrl().$url; + } + + // add a query string if needed + $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, function ($a, $b) { + return $a == $b ? 0 : 1; + }); + + // extract fragment + $fragment = ''; + if (isset($defaults['_fragment'])) { + $fragment = $defaults['_fragment']; + } + + if (isset($extra['_fragment'])) { + $fragment = $extra['_fragment']; + unset($extra['_fragment']); + } + + if ($extra && $query = http_build_query($extra, '', '&', PHP_QUERY_RFC3986)) { + // "/" and "?" can be left decoded for better user experience, see + // http://tools.ietf.org/html/rfc3986#section-3.4 + $url .= '?'.strtr($query, array('%2F' => '/')); + } + + if ('' !== $fragment) { + $url .= '#'.strtr(rawurlencode($fragment), array('%2F' => '/', '%3F' => '?')); + } + + return $url; + } + + /** + * Returns the target path as relative reference from the base path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @param string $basePath The base path + * @param string $targetPath The target path + * + * @return string The relative target path + */ + public static function getRelativePath($basePath, $targetPath) + { + if ($basePath === $targetPath) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see http://tools.ietf.org/html/rfc3986#section-4.2). + return '' === $path || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } +} diff --git a/vendor/symfony/routing/Generator/UrlGeneratorInterface.php b/vendor/symfony/routing/Generator/UrlGeneratorInterface.php new file mode 100644 index 00000000..d6e7938e --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGeneratorInterface.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * UrlGeneratorInterface is the interface that all URL generator classes must implement. + * + * The constants in this interface define the different types of resource references that + * are declared in RFC 3986: http://tools.ietf.org/html/rfc3986 + * We are using the term "URL" instead of "URI" as this is more common in web applications + * and we do not need to distinguish them as the difference is mostly semantical and + * less technical. Generating URIs, i.e. representation-independent resource identifiers, + * is also possible. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface UrlGeneratorInterface extends RequestContextAwareInterface +{ + /** + * Generates an absolute URL, e.g. "http://example.com/dir/file". + */ + const ABSOLUTE_URL = 0; + + /** + * Generates an absolute path, e.g. "/dir/file". + */ + const ABSOLUTE_PATH = 1; + + /** + * Generates a relative path based on the current request path, e.g. "../parent-file". + * + * @see UrlGenerator::getRelativePath() + */ + const RELATIVE_PATH = 2; + + /** + * Generates a network path, e.g. "//example.com/dir/file". + * Such reference reuses the current scheme but specifies the host. + */ + const NETWORK_PATH = 3; + + /** + * Generates a URL or path for a specific route based on the given parameters. + * + * Parameters that reference placeholders in the route pattern will substitute them in the + * path or host. Extra params are added as query string to the URL. + * + * When the passed reference type cannot be generated for the route because it requires a different + * host or scheme than the current one, the method will return a more comprehensive reference + * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH + * but the route requires the https scheme whereas the current scheme is http, it will instead return an + * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches + * the route in any case. + * + * If there is no route with the given name, the generator must throw the RouteNotFoundException. + * + * The special parameter _fragment will be used as the document fragment suffixed to the final URL. + * + * @param string $name The name of the route + * @param mixed $parameters An array of parameters + * @param int $referenceType The type of reference to be generated (one of the constants) + * + * @return string The generated URL + * + * @throws RouteNotFoundException If the named route doesn't exist + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH); +} diff --git a/vendor/symfony/routing/LICENSE b/vendor/symfony/routing/LICENSE new file mode 100644 index 00000000..17d16a13 --- /dev/null +++ b/vendor/symfony/routing/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/routing/Loader/AnnotationClassLoader.php b/vendor/symfony/routing/Loader/AnnotationClassLoader.php new file mode 100644 index 00000000..c91a7f7d --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationClassLoader.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Doctrine\Common\Annotations\Reader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Loader\LoaderResolverInterface; + +/** + * AnnotationClassLoader loads routing information from a PHP class and its methods. + * + * You need to define an implementation for the getRouteDefaults() method. Most of the + * time, this method should define some PHP callable to be called for the route + * (a controller in MVC speak). + * + * The @Route annotation can be set on the class (for global parameters), + * and on each method. + * + * The @Route annotation main value is the route path. The annotation also + * recognizes several parameters: requirements, options, defaults, schemes, + * methods, host, and name. The name parameter is mandatory. + * Here is an example of how you should be able to use it: + * + * /** + * * @Route("/Blog") + * * / + * class Blog + * { + * /** + * * @Route("/", name="blog_index") + * * / + * public function index() + * { + * } + * + * /** + * * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"}) + * * / + * public function show() + * { + * } + * } + * + * @author Fabien Potencier + */ +abstract class AnnotationClassLoader implements LoaderInterface +{ + /** + * @var Reader + */ + protected $reader; + + /** + * @var string + */ + protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route'; + + /** + * @var int + */ + protected $defaultRouteIndex = 0; + + /** + * Constructor. + * + * @param Reader $reader + */ + public function __construct(Reader $reader) + { + $this->reader = $reader; + } + + /** + * Sets the annotation class to read route properties from. + * + * @param string $class A fully-qualified class name + */ + public function setRouteAnnotationClass($class) + { + $this->routeAnnotationClass = $class; + } + + /** + * Loads from annotations from a class. + * + * @param string $class A class name + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When route can't be parsed + */ + public function load($class, $type = null) + { + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + $class = new \ReflectionClass($class); + if ($class->isAbstract()) { + throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class->getName())); + } + + $globals = $this->getGlobals($class); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($class->getFileName())); + + foreach ($class->getMethods() as $method) { + $this->defaultRouteIndex = 0; + foreach ($this->reader->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $this->routeAnnotationClass) { + $this->addRoute($collection, $annot, $globals, $class, $method); + } + } + } + + if (0 === $collection->count() && $class->hasMethod('__invoke') && $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + $globals['path'] = ''; + $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); + } + + return $collection; + } + + protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method) + { + $name = $annot->getName(); + if (null === $name) { + $name = $this->getDefaultRouteName($class, $method); + } + + $defaults = array_replace($globals['defaults'], $annot->getDefaults()); + foreach ($method->getParameters() as $param) { + if (false !== strpos($globals['path'].$annot->getPath(), sprintf('{%s}', $param->getName())) && !isset($defaults[$param->getName()]) && $param->isDefaultValueAvailable()) { + $defaults[$param->getName()] = $param->getDefaultValue(); + } + } + $requirements = array_replace($globals['requirements'], $annot->getRequirements()); + $options = array_replace($globals['options'], $annot->getOptions()); + $schemes = array_merge($globals['schemes'], $annot->getSchemes()); + $methods = array_merge($globals['methods'], $annot->getMethods()); + + $host = $annot->getHost(); + if (null === $host) { + $host = $globals['host']; + } + + $condition = $annot->getCondition(); + if (null === $condition) { + $condition = $globals['condition']; + } + + $route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + + $this->configureRoute($route, $class, $method, $annot); + + $collection->add($name, $route); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type); + } + + /** + * {@inheritdoc} + */ + public function setResolver(LoaderResolverInterface $resolver) + { + } + + /** + * {@inheritdoc} + */ + public function getResolver() + { + } + + /** + * Gets the default route name for a class method. + * + * @param \ReflectionClass $class + * @param \ReflectionMethod $method + * + * @return string + */ + protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) + { + $name = strtolower(str_replace('\\', '_', $class->name).'_'.$method->name); + if ($this->defaultRouteIndex > 0) { + $name .= '_'.$this->defaultRouteIndex; + } + ++$this->defaultRouteIndex; + + return $name; + } + + protected function getGlobals(\ReflectionClass $class) + { + $globals = array( + 'path' => '', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'schemes' => array(), + 'methods' => array(), + 'host' => '', + 'condition' => '', + ); + + if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + if (null !== $annot->getPath()) { + $globals['path'] = $annot->getPath(); + } + + if (null !== $annot->getRequirements()) { + $globals['requirements'] = $annot->getRequirements(); + } + + if (null !== $annot->getOptions()) { + $globals['options'] = $annot->getOptions(); + } + + if (null !== $annot->getDefaults()) { + $globals['defaults'] = $annot->getDefaults(); + } + + if (null !== $annot->getSchemes()) { + $globals['schemes'] = $annot->getSchemes(); + } + + if (null !== $annot->getMethods()) { + $globals['methods'] = $annot->getMethods(); + } + + if (null !== $annot->getHost()) { + $globals['host'] = $annot->getHost(); + } + + if (null !== $annot->getCondition()) { + $globals['condition'] = $annot->getCondition(); + } + } + + return $globals; + } + + protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition) + { + return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + } + + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot); +} diff --git a/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php b/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php new file mode 100644 index 00000000..616d01ef --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\DirectoryResource; + +/** + * AnnotationDirectoryLoader loads routing information from annotations set + * on PHP classes and methods. + * + * @author Fabien Potencier + */ +class AnnotationDirectoryLoader extends AnnotationFileLoader +{ + /** + * Loads from annotations from a directory. + * + * @param string $path A directory path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed + */ + public function load($path, $type = null) + { + $dir = $this->locator->locate($path); + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($dir, '/\.php$/')); + $files = iterator_to_array(new \RecursiveIteratorIterator( + new \RecursiveCallbackFilterIterator( + new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + function (\SplFileInfo $current) { + return '.' !== substr($current->getBasename(), 0, 1); + } + ), + \RecursiveIteratorIterator::LEAVES_ONLY + )); + usort($files, function (\SplFileInfo $a, \SplFileInfo $b) { + return (string) $a > (string) $b ? 1 : -1; + }); + + foreach ($files as $file) { + if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) { + continue; + } + + if ($class = $this->findClass($file)) { + $refl = new \ReflectionClass($class); + if ($refl->isAbstract()) { + continue; + } + + $collection->addCollection($this->loader->load($class, $type)); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + if (!is_string($resource)) { + return false; + } + + try { + $path = $this->locator->locate($resource); + } catch (\Exception $e) { + return false; + } + + return is_dir($path) && (!$type || 'annotation' === $type); + } +} diff --git a/vendor/symfony/routing/Loader/AnnotationFileLoader.php b/vendor/symfony/routing/Loader/AnnotationFileLoader.php new file mode 100644 index 00000000..22edc867 --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationFileLoader.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\FileLocatorInterface; + +/** + * AnnotationFileLoader loads routing information from annotations set + * on a PHP class and its methods. + * + * @author Fabien Potencier + */ +class AnnotationFileLoader extends FileLoader +{ + protected $loader; + + /** + * Constructor. + * + * @param FileLocatorInterface $locator A FileLocator instance + * @param AnnotationClassLoader $loader An AnnotationClassLoader instance + * + * @throws \RuntimeException + */ + public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader) + { + if (!function_exists('token_get_all')) { + throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.'); + } + + parent::__construct($locator); + + $this->loader = $loader; + } + + /** + * Loads from annotations from a file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + if ($class = $this->findClass($path)) { + $collection->addResource(new FileResource($path)); + $collection->addCollection($this->loader->load($class, $type)); + } + if (\PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + gc_mem_caches(); + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type); + } + + /** + * Returns the full class name for the first class in the file. + * + * @param string $file A PHP file path + * + * @return string|false Full class name if found, false otherwise + */ + protected function findClass($file) + { + $class = false; + $namespace = false; + $tokens = token_get_all(file_get_contents($file)); + + if (1 === count($tokens) && T_INLINE_HTML === $tokens[0][0]) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain PHP code. Did you forgot to add the " 0; --$j) { + if (!isset($tokens[$j][1])) { + break; + } + + if (T_DOUBLE_COLON === $tokens[$j][0]) { + $isClassConstant = true; + break; + } elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) { + break; + } + } + + if (!$isClassConstant) { + $class = true; + } + } + + if (T_NAMESPACE === $token[0]) { + $namespace = true; + } + } + + return false; + } +} diff --git a/vendor/symfony/routing/Loader/ClosureLoader.php b/vendor/symfony/routing/Loader/ClosureLoader.php new file mode 100644 index 00000000..5df9f6ae --- /dev/null +++ b/vendor/symfony/routing/Loader/ClosureLoader.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Routing\RouteCollection; + +/** + * ClosureLoader loads routes from a PHP closure. + * + * The Closure must return a RouteCollection instance. + * + * @author Fabien Potencier + */ +class ClosureLoader extends Loader +{ + /** + * Loads a Closure. + * + * @param \Closure $closure A Closure + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + */ + public function load($closure, $type = null) + { + return $closure(); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return $resource instanceof \Closure && (!$type || 'closure' === $type); + } +} diff --git a/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php b/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php new file mode 100644 index 00000000..6c162163 --- /dev/null +++ b/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Routing\Loader\ObjectRouteLoader; + +/** + * A route loader that executes a service to load the routes. + * + * This depends on the DependencyInjection component. + * + * @author Ryan Weaver + */ +class ServiceRouterLoader extends ObjectRouteLoader +{ + /** + * @var ContainerInterface + */ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getServiceObject($id) + { + return $this->container->get($id); + } +} diff --git a/vendor/symfony/routing/Loader/DirectoryLoader.php b/vendor/symfony/routing/Loader/DirectoryLoader.php new file mode 100644 index 00000000..4bb5b31b --- /dev/null +++ b/vendor/symfony/routing/Loader/DirectoryLoader.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\DirectoryResource; + +class DirectoryLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($path)); + + foreach (scandir($path) as $dir) { + if ('.' !== $dir[0]) { + $this->setCurrentDir($path); + $subPath = $path.'/'.$dir; + $subType = null; + + if (is_dir($subPath)) { + $subPath .= '/'; + $subType = 'directory'; + } + + $subCollection = $this->import($subPath, $subType, false, $path); + $collection->addCollection($subCollection); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + // only when type is forced to directory, not to conflict with AnnotationLoader + + return 'directory' === $type; + } +} diff --git a/vendor/symfony/routing/Loader/ObjectRouteLoader.php b/vendor/symfony/routing/Loader/ObjectRouteLoader.php new file mode 100644 index 00000000..4d79b6cf --- /dev/null +++ b/vendor/symfony/routing/Loader/ObjectRouteLoader.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * A route loader that calls a method on an object to load the routes. + * + * @author Ryan Weaver + */ +abstract class ObjectRouteLoader extends Loader +{ + /** + * Returns the object that the method will be called on to load routes. + * + * For example, if your application uses a service container, + * the $id may be a service id. + * + * @param string $id + * + * @return object + */ + abstract protected function getServiceObject($id); + + /** + * Calls the service that will load the routes. + * + * @param mixed $resource Some value that will resolve to a callable + * @param string|null $type The resource type + * + * @return RouteCollection + */ + public function load($resource, $type = null) + { + $parts = explode(':', $resource); + if (count($parts) != 2) { + throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service_name:methodName"', $resource)); + } + + $serviceString = $parts[0]; + $method = $parts[1]; + + $loaderObject = $this->getServiceObject($serviceString); + + if (!is_object($loaderObject)) { + throw new \LogicException(sprintf('%s:getServiceObject() must return an object: %s returned', get_class($this), gettype($loaderObject))); + } + + if (!method_exists($loaderObject, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s"', $method, get_class($loaderObject), $resource)); + } + + $routeCollection = call_user_func(array($loaderObject, $method), $this); + + if (!$routeCollection instanceof RouteCollection) { + $type = is_object($routeCollection) ? get_class($routeCollection) : gettype($routeCollection); + + throw new \LogicException(sprintf('The %s::%s method must return a RouteCollection: %s returned', get_class($loaderObject), $method, $type)); + } + + // make the service file tracked so that if it changes, the cache rebuilds + $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection); + + return $routeCollection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return 'service' === $type; + } + + private function addClassResource(\ReflectionClass $class, RouteCollection $collection) + { + do { + if (is_file($class->getFileName())) { + $collection->addResource(new FileResource($class->getFileName())); + } + } while ($class = $class->getParentClass()); + } +} diff --git a/vendor/symfony/routing/Loader/PhpFileLoader.php b/vendor/symfony/routing/Loader/PhpFileLoader.php new file mode 100644 index 00000000..b4ba5fbc --- /dev/null +++ b/vendor/symfony/routing/Loader/PhpFileLoader.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * PhpFileLoader loads routes from a PHP file. + * + * The file must return a RouteCollection instance. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends FileLoader +{ + /** + * Loads a PHP file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + $this->setCurrentDir(dirname($path)); + + $collection = self::includeFile($path, $this); + $collection->addResource(new FileResource($path)); + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type); + } + + /** + * Safe include. Used for scope isolation. + * + * @param string $file File to include + * @param PhpFileLoader $loader the loader variable is exposed to the included file below + * + * @return RouteCollection + */ + private static function includeFile($file, PhpFileLoader $loader) + { + return include $file; + } +} diff --git a/vendor/symfony/routing/Loader/XmlFileLoader.php b/vendor/symfony/routing/Loader/XmlFileLoader.php new file mode 100644 index 00000000..aff0ba27 --- /dev/null +++ b/vendor/symfony/routing/Loader/XmlFileLoader.php @@ -0,0 +1,342 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Util\XmlUtils; + +/** + * XmlFileLoader loads XML routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class XmlFileLoader extends FileLoader +{ + const NAMESPACE_URI = 'http://symfony.com/schema/routing'; + const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the file cannot be loaded or when the XML cannot be + * parsed because it does not validate against the scheme. + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $xml = $this->loadFile($path); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // process routes and imports + foreach ($xml->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement) { + continue; + } + + $this->parseNode($collection, $node, $path, $file); + } + + return $collection; + } + + /** + * Parses a node from a loaded XML file. + * + * @param RouteCollection $collection Collection to associate with the node + * @param \DOMElement $node Element to parse + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if (self::NAMESPACE_URI !== $node->namespaceURI) { + return; + } + + switch ($node->localName) { + case 'route': + $this->parseRoute($collection, $node, $path); + break; + case 'import': + $this->parseImport($collection, $node, $path, $file); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); + } + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path) + { + if ('' === ($id = $node->getAttribute('id')) || !$node->hasAttribute('path')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" and a "path" attribute.', $path)); + } + + $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY); + $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY); + + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + + $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition); + $collection->add($id, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if ('' === $resource = $node->getAttribute('resource')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute.', $path)); + } + + $type = $node->getAttribute('type'); + $prefix = $node->getAttribute('prefix'); + $host = $node->hasAttribute('host') ? $node->getAttribute('host') : null; + $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null; + $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null; + + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + + $this->setCurrentDir(dirname($path)); + + $subCollection = $this->import($resource, ('' !== $type ? $type : null), false, $file); + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * + * @return \DOMDocument + * + * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors + * or when the XML structure is not as expected by the scheme - + * see validate() + */ + protected function loadFile($file) + { + return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH); + } + + /** + * Parses the config elements (default, requirement, option). + * + * @param \DOMElement $node Element to parse that contains the configs + * @param string $path Full path of the XML file being processed + * + * @return array An array with the defaults as first item, requirements as second and options as third + * + * @throws \InvalidArgumentException When the XML is invalid + */ + private function parseConfigs(\DOMElement $node, $path) + { + $defaults = array(); + $requirements = array(); + $options = array(); + $condition = null; + + foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { + if ($node !== $n->parentNode) { + continue; + } + + switch ($n->localName) { + case 'default': + if ($this->isElementValueNull($n)) { + $defaults[$n->getAttribute('key')] = null; + } else { + $defaults[$n->getAttribute('key')] = $this->parseDefaultsConfig($n, $path); + } + + break; + case 'requirement': + $requirements[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'option': + $options[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'condition': + $condition = trim($n->textContent); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement" or "option".', $n->localName, $path)); + } + } + + return array($defaults, $requirements, $options, $condition); + } + + /** + * Parses the "default" elements. + * + * @param \DOMElement $element The "default" element to parse + * @param string $path Full path of the XML file being processed + * + * @return array|bool|float|int|string|null The parsed value of the "default" element + */ + private function parseDefaultsConfig(\DOMElement $element, $path) + { + if ($this->isElementValueNull($element)) { + return; + } + + // Check for existing element nodes in the default element. There can + // only be a single element inside a default element. So this element + // (if one was found) can safely be returned. + foreach ($element->childNodes as $child) { + if (!$child instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $child->namespaceURI) { + continue; + } + + return $this->parseDefaultNode($child, $path); + } + + // If the default element doesn't contain a nested "bool", "int", "float", + // "string", "list", or "map" element, the element contents will be treated + // as the string value of the associated default option. + return trim($element->textContent); + } + + /** + * Recursively parses the value of a "default" element. + * + * @param \DOMElement $node The node value + * @param string $path Full path of the XML file being processed + * + * @return array|bool|float|int|string The parsed value + * + * @throws \InvalidArgumentException when the XML is invalid + */ + private function parseDefaultNode(\DOMElement $node, $path) + { + if ($this->isElementValueNull($node)) { + return; + } + + switch ($node->localName) { + case 'bool': + return 'true' === trim($node->nodeValue) || '1' === trim($node->nodeValue); + case 'int': + return (int) trim($node->nodeValue); + case 'float': + return (float) trim($node->nodeValue); + case 'string': + return trim($node->nodeValue); + case 'list': + $list = array(); + + foreach ($node->childNodes as $element) { + if (!$element instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $element->namespaceURI) { + continue; + } + + $list[] = $this->parseDefaultNode($element, $path); + } + + return $list; + case 'map': + $map = array(); + + foreach ($node->childNodes as $element) { + if (!$element instanceof \DOMElement) { + continue; + } + + if (self::NAMESPACE_URI !== $element->namespaceURI) { + continue; + } + + $map[$element->getAttribute('key')] = $this->parseDefaultNode($element, $path); + } + + return $map; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "bool", "int", "float", "string", "list", or "map".', $node->localName, $path)); + } + } + + private function isElementValueNull(\DOMElement $element) + { + $namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + + if (!$element->hasAttributeNS($namespaceUri, 'nil')) { + return false; + } + + return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil'); + } +} diff --git a/vendor/symfony/routing/Loader/YamlFileLoader.php b/vendor/symfony/routing/Loader/YamlFileLoader.php new file mode 100644 index 00000000..7ae5e84d --- /dev/null +++ b/vendor/symfony/routing/Loader/YamlFileLoader.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileLoader loads Yaml routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class YamlFileLoader extends FileLoader +{ + private static $availableKeys = array( + 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', + ); + private $yamlParser; + + /** + * Loads a Yaml file. + * + * @param string $file A Yaml file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + if (!stream_is_local($path)) { + throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $path)); + } + + if (!file_exists($path)) { + throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path)); + } + + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + try { + $parsedConfig = $this->yamlParser->parse(file_get_contents($path), Yaml::PARSE_KEYS_AS_STRINGS); + } catch (ParseException $e) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e); + } + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // empty file + if (null === $parsedConfig) { + return $collection; + } + + // not an array + if (!is_array($parsedConfig)) { + throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path)); + } + + foreach ($parsedConfig as $name => $config) { + $this->validate($config, $name, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true) && (!$type || 'yaml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param string $name Route name + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + */ + protected function parseRoute(RouteCollection $collection, $name, array $config, $path) + { + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : ''; + $schemes = isset($config['schemes']) ? $config['schemes'] : array(); + $methods = isset($config['methods']) ? $config['methods'] : array(); + $condition = isset($config['condition']) ? $config['condition'] : null; + + $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + + $collection->add($name, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + * @param string $file Loaded file name + */ + protected function parseImport(RouteCollection $collection, array $config, $path, $file) + { + $type = isset($config['type']) ? $config['type'] : null; + $prefix = isset($config['prefix']) ? $config['prefix'] : ''; + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : null; + $condition = isset($config['condition']) ? $config['condition'] : null; + $schemes = isset($config['schemes']) ? $config['schemes'] : null; + $methods = isset($config['methods']) ? $config['methods'] : null; + + $this->setCurrentDir(dirname($path)); + + $subCollection = $this->import($config['resource'], $type, false, $file); + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + + /** + * Validates the route configuration. + * + * @param array $config A resource config + * @param string $name The config key + * @param string $path The loaded file path + * + * @throws \InvalidArgumentException If one of the provided config keys is not supported, + * something is missing or the combination is nonsense + */ + protected function validate($config, $name, $path) + { + if (!is_array($config)) { + throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path)); + } + if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) { + throw new \InvalidArgumentException(sprintf( + 'The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', + $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys) + )); + } + if (isset($config['resource']) && isset($config['path'])) { + throw new \InvalidArgumentException(sprintf( + 'The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.', + $path, $name + )); + } + if (!isset($config['resource']) && isset($config['type'])) { + throw new \InvalidArgumentException(sprintf( + 'The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.', + $name, $path + )); + } + if (!isset($config['resource']) && !isset($config['path'])) { + throw new \InvalidArgumentException(sprintf( + 'You must define a "path" for the route "%s" in file "%s".', + $name, $path + )); + } + } +} diff --git a/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd b/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd new file mode 100644 index 00000000..92d4ae20 --- /dev/null +++ b/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php b/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php new file mode 100644 index 00000000..b24c8512 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Collection of routes. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperCollection implements \IteratorAggregate +{ + /** + * @var DumperCollection|null + */ + private $parent; + + /** + * @var DumperCollection[]|DumperRoute[] + */ + private $children = array(); + + /** + * @var array + */ + private $attributes = array(); + + /** + * Returns the children routes and collections. + * + * @return self[]|DumperRoute[] + */ + public function all() + { + return $this->children; + } + + /** + * Adds a route or collection. + * + * @param DumperRoute|DumperCollection The route or collection + */ + public function add($child) + { + if ($child instanceof self) { + $child->setParent($this); + } + $this->children[] = $child; + } + + /** + * Sets children. + * + * @param array $children The children + */ + public function setAll(array $children) + { + foreach ($children as $child) { + if ($child instanceof self) { + $child->setParent($this); + } + } + $this->children = $children; + } + + /** + * Returns an iterator over the children. + * + * @return \Iterator|DumperCollection[]|DumperRoute[] The iterator + */ + public function getIterator() + { + return new \ArrayIterator($this->children); + } + + /** + * Returns the root of the collection. + * + * @return self The root collection + */ + public function getRoot() + { + return (null !== $this->parent) ? $this->parent->getRoot() : $this; + } + + /** + * Returns the parent collection. + * + * @return self|null The parent collection or null if the collection has no parent + */ + protected function getParent() + { + return $this->parent; + } + + /** + * Sets the parent collection. + * + * @param DumperCollection $parent The parent collection + */ + protected function setParent(DumperCollection $parent) + { + $this->parent = $parent; + } + + /** + * Returns true if the attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function hasAttribute($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * Returns an attribute by name. + * + * @param string $name The attribute name + * @param mixed $default Default value is the attribute doesn't exist + * + * @return mixed The attribute value + */ + public function getAttribute($name, $default = null) + { + return $this->hasAttribute($name) ? $this->attributes[$name] : $default; + } + + /** + * Sets an attribute by name. + * + * @param string $name The attribute name + * @param mixed $value The attribute value + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * Sets multiple attributes. + * + * @param array $attributes The attributes + */ + public function setAttributes($attributes) + { + $this->attributes = $attributes; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php b/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php new file mode 100644 index 00000000..3ad08c20 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; + +/** + * Container for a Route. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperRoute +{ + /** + * @var string + */ + private $name; + + /** + * @var Route + */ + private $route; + + /** + * Constructor. + * + * @param string $name The route name + * @param Route $route The route + */ + public function __construct($name, Route $route) + { + $this->name = $name; + $this->route = $route; + } + + /** + * Returns the route name. + * + * @return string The route name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the route. + * + * @return Route The route + */ + public function getRoute() + { + return $this->route; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php b/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php new file mode 100644 index 00000000..52edc017 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumper is the abstract class for all built-in matcher dumpers. + * + * @author Fabien Potencier + */ +abstract class MatcherDumper implements MatcherDumperInterface +{ + /** + * @var RouteCollection + */ + private $routes; + + /** + * Constructor. + * + * @param RouteCollection $routes The RouteCollection to dump + */ + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php b/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php new file mode 100644 index 00000000..5e7c134b --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumperInterface is the interface that all matcher dumper classes must implement. + * + * @author Fabien Potencier + */ +interface MatcherDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to match a request against these routes. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php b/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php new file mode 100644 index 00000000..8eae68c4 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php @@ -0,0 +1,431 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + * @author Arnaud Le Blanc + */ +class PhpMatcherDumper extends MatcherDumper +{ + private $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the matcher class + */ + public function dump(array $options = array()) + { + $options = array_replace(array( + 'class' => 'ProjectUrlMatcher', + 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + ), $options); + + // trailing slash support is only enabled if we know how to redirect the user + $interfaces = class_implements($options['base_class']); + $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); + + return <<context = \$context; + } + +{$this->generateMatchMethod($supportsRedirections)} +} + +EOF; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Generates the code for the match method implementing UrlMatcherInterface. + * + * @param bool $supportsRedirections Whether redirections are supported by the base class + * + * @return string Match method as PHP code + */ + private function generateMatchMethod($supportsRedirections) + { + $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n"); + + return <<context; + \$request = \$this->request; + \$requestMethod = \$canonicalMethod = \$context->getMethod(); + \$scheme = \$context->getScheme(); + + if ('HEAD' === \$requestMethod) { + \$canonicalMethod = 'GET'; + } + + +$code + + throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); + } +EOF; + } + + /** + * Generates PHP code to match a RouteCollection with all its routes. + * + * @param RouteCollection $routes A RouteCollection instance + * @param bool $supportsRedirections Whether redirections are supported by the base class + * + * @return string PHP code + */ + private function compileRoutes(RouteCollection $routes, $supportsRedirections) + { + $fetchedHost = false; + $groups = $this->groupRoutesByHostRegex($routes); + $code = ''; + + foreach ($groups as $collection) { + if (null !== $regex = $collection->getAttribute('host_regex')) { + if (!$fetchedHost) { + $code .= " \$host = \$context->getHost();\n\n"; + $fetchedHost = true; + } + + $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true)); + } + + $tree = $this->buildStaticPrefixCollection($collection); + $groupCode = $this->compileStaticPrefixRoutes($tree, $supportsRedirections); + + if (null !== $regex) { + // apply extra indention at each line (except empty ones) + $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode); + $code .= $groupCode; + $code .= " }\n\n"; + } else { + $code .= $groupCode; + } + } + + return $code; + } + + private function buildStaticPrefixCollection(DumperCollection $collection) + { + $prefixCollection = new StaticPrefixCollection(); + + foreach ($collection as $dumperRoute) { + $prefix = $dumperRoute->getRoute()->compile()->getStaticPrefix(); + $prefixCollection->addRoute($prefix, $dumperRoute); + } + + $prefixCollection->optimizeGroups(); + + return $prefixCollection; + } + + /** + * Generates PHP code to match a tree of routes. + * + * @param StaticPrefixCollection $collection A StaticPrefixCollection instance + * @param bool $supportsRedirections Whether redirections are supported by the base class + * @param string $ifOrElseIf Either "if" or "elseif" to influence chaining. + * + * @return string PHP code + */ + private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $supportsRedirections, $ifOrElseIf = 'if') + { + $code = ''; + $prefix = $collection->getPrefix(); + + if (!empty($prefix) && '/' !== $prefix) { + $code .= sprintf(" %s (0 === strpos(\$pathinfo, %s)) {\n", $ifOrElseIf, var_export($prefix, true)); + } + + $ifOrElseIf = 'if'; + + foreach ($collection->getItems() as $route) { + if ($route instanceof StaticPrefixCollection) { + $code .= $this->compileStaticPrefixRoutes($route, $supportsRedirections, $ifOrElseIf); + $ifOrElseIf = 'elseif'; + } else { + $code .= $this->compileRoute($route[1]->getRoute(), $route[1]->getName(), $supportsRedirections, $prefix)."\n"; + $ifOrElseIf = 'if'; + } + } + + if (!empty($prefix) && '/' !== $prefix) { + $code .= " }\n\n"; + // apply extra indention at each line (except empty ones) + $code = preg_replace('/^.{2,}$/m', ' $0', $code); + } + + return $code; + } + + /** + * Compiles a single Route to PHP code used to match it against the path info. + * + * @param Route $route A Route instance + * @param string $name The name of the Route + * @param bool $supportsRedirections Whether redirections are supported by the base class + * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code + * + * @return string PHP code + * + * @throws \LogicException + */ + private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) + { + $code = ''; + $compiledRoute = $route->compile(); + $conditions = array(); + $hasTrailingSlash = false; + $matches = false; + $hostMatches = false; + $methods = $route->getMethods(); + + $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods) || in_array('GET', $methods)); + $regex = $compiledRoute->getRegex(); + + if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#'.(substr($regex, -1) === 'u' ? 'u' : ''), $regex, $m)) { + if ($supportsTrailingSlash && substr($m['url'], -1) === '/') { + $conditions[] = sprintf('%s === $trimmedPathinfo', var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); + $hasTrailingSlash = true; + } else { + $conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true)); + } + } else { + if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) { + $conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true)); + } + + if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { + $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); + $hasTrailingSlash = true; + } + $conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true)); + + $matches = true; + } + + if ($compiledRoute->getHostVariables()) { + $hostMatches = true; + } + + if ($route->getCondition()) { + $conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request')); + } + + $conditions = implode(' && ', $conditions); + + $code .= <<redirect(\$pathinfo.'/', '$name'); + } + + +EOF; + } + + if ($schemes = $route->getSchemes()) { + if (!$supportsRedirections) { + throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); + } + $schemes = str_replace("\n", '', var_export(array_flip($schemes), true)); + $code .= <<redirect(\$pathinfo, '$name', key(\$requiredSchemes)); + } + + +EOF; + } + + // optimize parameters array + if ($matches || $hostMatches) { + $vars = array(); + if ($hostMatches) { + $vars[] = '$hostMatches'; + } + if ($matches) { + $vars[] = '$matches'; + } + $vars[] = "array('_route' => '$name')"; + + $code .= sprintf( + " return \$this->mergeDefaults(array_replace(%s), %s);\n", + implode(', ', $vars), + str_replace("\n", '', var_export($route->getDefaults(), true)) + ); + } elseif ($route->getDefaults()) { + $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true))); + } else { + $code .= sprintf(" return array('_route' => '%s');\n", $name); + } + $code .= " }\n"; + + if ($methods) { + $code .= " $gotoname:\n"; + } + + return $code; + } + + /** + * Groups consecutive routes having the same host regex. + * + * The result is a collection of collections of routes having the same host regex. + * + * @param RouteCollection $routes A flat RouteCollection + * + * @return DumperCollection A collection with routes grouped by host regex in sub-collections + */ + private function groupRoutesByHostRegex(RouteCollection $routes) + { + $groups = new DumperCollection(); + $currentGroup = new DumperCollection(); + $currentGroup->setAttribute('host_regex', null); + $groups->add($currentGroup); + + foreach ($routes as $name => $route) { + $hostRegex = $route->compile()->getHostRegex(); + if ($currentGroup->getAttribute('host_regex') !== $hostRegex) { + $currentGroup = new DumperCollection(); + $currentGroup->setAttribute('host_regex', $hostRegex); + $groups->add($currentGroup); + } + $currentGroup->add(new DumperRoute($name, $route)); + } + + return $groups; + } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php b/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php new file mode 100644 index 00000000..b2bfa343 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Prefix tree of routes preserving routes order. + * + * @author Frank de Jonge + * + * @internal + */ +class StaticPrefixCollection +{ + /** + * @var string + */ + private $prefix; + + /** + * @var array[]|StaticPrefixCollection[] + */ + private $items = array(); + + /** + * @var int + */ + private $matchStart = 0; + + public function __construct($prefix = '') + { + $this->prefix = $prefix; + } + + public function getPrefix() + { + return $this->prefix; + } + + /** + * @return mixed[]|StaticPrefixCollection[] + */ + public function getItems() + { + return $this->items; + } + + /** + * Adds a route to a group. + * + * @param string $prefix + * @param mixed $route + */ + public function addRoute($prefix, $route) + { + $prefix = '/' === $prefix ? $prefix : rtrim($prefix, '/'); + $this->guardAgainstAddingNotAcceptedRoutes($prefix); + + if ($this->prefix === $prefix) { + // When a prefix is exactly the same as the base we move up the match start position. + // This is needed because otherwise routes that come afterwards have higher precedence + // than a possible regular expression, which goes against the input order sorting. + $this->items[] = array($prefix, $route); + $this->matchStart = count($this->items); + + return; + } + + foreach ($this->items as $i => $item) { + if ($i < $this->matchStart) { + continue; + } + + if ($item instanceof self && $item->accepts($prefix)) { + $item->addRoute($prefix, $route); + + return; + } + + $group = $this->groupWithItem($item, $prefix, $route); + + if ($group instanceof self) { + $this->items[$i] = $group; + + return; + } + } + + // No optimised case was found, in this case we simple add the route for possible + // grouping when new routes are added. + $this->items[] = array($prefix, $route); + } + + /** + * Tries to combine a route with another route or group. + * + * @param StaticPrefixCollection|array $item + * @param string $prefix + * @param mixed $route + * + * @return null|StaticPrefixCollection + */ + private function groupWithItem($item, $prefix, $route) + { + $itemPrefix = $item instanceof self ? $item->prefix : $item[0]; + $commonPrefix = $this->detectCommonPrefix($prefix, $itemPrefix); + + if (!$commonPrefix) { + return; + } + + $child = new self($commonPrefix); + + if ($item instanceof self) { + $child->items = array($item); + } else { + $child->addRoute($item[0], $item[1]); + } + + $child->addRoute($prefix, $route); + + return $child; + } + + /** + * Checks whether a prefix can be contained within the group. + * + * @param string $prefix + * + * @return bool Whether a prefix could belong in a given group + */ + private function accepts($prefix) + { + return '' === $this->prefix || strpos($prefix, $this->prefix) === 0; + } + + /** + * Detects whether there's a common prefix relative to the group prefix and returns it. + * + * @param string $prefix + * @param string $anotherPrefix + * + * @return false|string A common prefix, longer than the base/group prefix, or false when none available + */ + private function detectCommonPrefix($prefix, $anotherPrefix) + { + $baseLength = strlen($this->prefix); + $commonLength = $baseLength; + $end = min(strlen($prefix), strlen($anotherPrefix)); + + for ($i = $baseLength; $i <= $end; ++$i) { + if (substr($prefix, 0, $i) !== substr($anotherPrefix, 0, $i)) { + break; + } + + $commonLength = $i; + } + + $commonPrefix = rtrim(substr($prefix, 0, $commonLength), '/'); + + if (strlen($commonPrefix) > $baseLength) { + return $commonPrefix; + } + + return false; + } + + /** + * Optimizes the tree by inlining items from groups with less than 3 items. + */ + public function optimizeGroups() + { + $index = -1; + + while (isset($this->items[++$index])) { + $item = $this->items[$index]; + + if ($item instanceof self) { + $item->optimizeGroups(); + + // When a group contains only two items there's no reason to optimize because at minimum + // the amount of prefix check is 2. In this case inline the group. + if ($item->shouldBeInlined()) { + array_splice($this->items, $index, 1, $item->items); + + // Lower index to pass through the same index again after optimizing. + // The first item of the replacements might be a group needing optimization. + --$index; + } + } + } + } + + private function shouldBeInlined() + { + if (count($this->items) >= 3) { + return false; + } + + foreach ($this->items as $item) { + if ($item instanceof self) { + return true; + } + } + + foreach ($this->items as $item) { + if (is_array($item) && $item[0] === $this->prefix) { + return false; + } + } + + return true; + } + + /** + * Guards against adding incompatible prefixes in a group. + * + * @param string $prefix + * + * @throws \LogicException When a prefix does not belong in a group. + */ + private function guardAgainstAddingNotAcceptedRoutes($prefix) + { + if (!$this->accepts($prefix)) { + $message = sprintf('Could not add route with prefix %s to collection with prefix %s', $prefix, $this->prefix); + + throw new \LogicException($message); + } + } +} diff --git a/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php b/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php new file mode 100644 index 00000000..463bc0d0 --- /dev/null +++ b/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Route; + +/** + * @author Fabien Potencier + */ +abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + try { + $parameters = parent::match($pathinfo); + } catch (ResourceNotFoundException $e) { + if ('/' === substr($pathinfo, -1) || !in_array($this->context->getMethod(), array('HEAD', 'GET'))) { + throw $e; + } + + try { + parent::match($pathinfo.'/'); + + return $this->redirect($pathinfo.'/', null); + } catch (ResourceNotFoundException $e2) { + throw $e; + } + } + + return $parameters; + } + + /** + * {@inheritdoc} + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + + // check HTTP scheme requirement + $scheme = $this->context->getScheme(); + $schemes = $route->getSchemes(); + if ($schemes && !$route->hasScheme($scheme)) { + return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, current($schemes))); + } + + return array(self::REQUIREMENT_MATCH, null); + } +} diff --git a/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php b/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php new file mode 100644 index 00000000..7c27bc87 --- /dev/null +++ b/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +/** + * RedirectableUrlMatcherInterface knows how to redirect the user. + * + * @author Fabien Potencier + */ +interface RedirectableUrlMatcherInterface +{ + /** + * Redirects the user to another URL. + * + * @param string $path The path info to redirect to + * @param string $route The route name that matched + * @param string|null $scheme The URL scheme (null to keep the current one) + * + * @return array An array of parameters + */ + public function redirect($path, $route, $scheme = null); +} diff --git a/vendor/symfony/routing/Matcher/RequestMatcherInterface.php b/vendor/symfony/routing/Matcher/RequestMatcherInterface.php new file mode 100644 index 00000000..b5def3d4 --- /dev/null +++ b/vendor/symfony/routing/Matcher/RequestMatcherInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * RequestMatcherInterface is the interface that all request matcher classes must implement. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Tries to match a request with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @param Request $request The request to match + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If no matching resource could be found + * @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed + */ + public function matchRequest(Request $request); +} diff --git a/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php b/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php new file mode 100644 index 00000000..cb1a35f4 --- /dev/null +++ b/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\ExceptionInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * TraceableUrlMatcher helps debug path info matching by tracing the match. + * + * @author Fabien Potencier + */ +class TraceableUrlMatcher extends UrlMatcher +{ + const ROUTE_DOES_NOT_MATCH = 0; + const ROUTE_ALMOST_MATCHES = 1; + const ROUTE_MATCHES = 2; + + protected $traces; + + public function getTraces($pathinfo) + { + $this->traces = array(); + + try { + $this->match($pathinfo); + } catch (ExceptionInterface $e) { + } + + return $this->traces; + } + + public function getTracesForRequest(Request $request) + { + $this->request = $request; + $traces = $this->getTraces($request->getPathInfo()); + $this->request = null; + + return $traces; + } + + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + // does it match without any requirements? + $r = new Route($route->getPath(), $route->getDefaults(), array(), $route->getOptions()); + $cr = $r->compile(); + if (!preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); + + continue; + } + + foreach ($route->getRequirements() as $n => $regex) { + $r = new Route($route->getPath(), $route->getDefaults(), array($n => $regex), $route->getOptions()); + $cr = $r->compile(); + + if (in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue 2; + } + } + + continue; + } + + // check host requirement + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + + // check HTTP method requirement + if ($requiredMethods = $route->getMethods()) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!in_array($method, $requiredMethods)) { + $this->allow = array_merge($this->allow, $requiredMethods); + + $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + + // check condition + if ($condition = $route->getCondition()) { + if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request))) { + $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + + // check HTTP scheme requirement + if ($requiredSchemes = $route->getSchemes()) { + $scheme = $this->context->getScheme(); + + if (!$route->hasScheme($scheme)) { + $this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s); the user will be redirected to first required scheme', $scheme, implode(', ', $requiredSchemes)), self::ROUTE_ALMOST_MATCHES, $name, $route); + + return true; + } + } + + $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); + + return true; + } + } + + private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null) + { + $this->traces[] = array( + 'log' => $log, + 'name' => $name, + 'level' => $level, + 'path' => null !== $route ? $route->getPath() : null, + ); + } +} diff --git a/vendor/symfony/routing/Matcher/UrlMatcher.php b/vendor/symfony/routing/Matcher/UrlMatcher.php new file mode 100644 index 00000000..9786a9b4 --- /dev/null +++ b/vendor/symfony/routing/Matcher/UrlMatcher.php @@ -0,0 +1,251 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * UrlMatcher matches URL based on a set of routes. + * + * @author Fabien Potencier + */ +class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface +{ + const REQUIREMENT_MATCH = 0; + const REQUIREMENT_MISMATCH = 1; + const ROUTE_MATCH = 2; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var array + */ + protected $allow = array(); + + /** + * @var RouteCollection + */ + protected $routes; + + protected $request; + protected $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + protected $expressionLanguageProviders = array(); + + /** + * Constructor. + * + * @param RouteCollection $routes A RouteCollection instance + * @param RequestContext $context The context + */ + public function __construct(RouteCollection $routes, RequestContext $context) + { + $this->routes = $routes; + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + $this->allow = array(); + + if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) { + return $ret; + } + + throw 0 < count($this->allow) + ? new MethodNotAllowedException(array_unique($this->allow)) + : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + $this->request = $request; + + $ret = $this->match($request->getPathInfo()); + + $this->request = null; + + return $ret; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Tries to match a URL with a set of routes. + * + * @param string $pathinfo The path info to be parsed + * @param RouteCollection $routes The set of routes + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + // check the static prefix of the URL first. Only use the more expensive preg_match when it matches + if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) { + continue; + } + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + continue; + } + + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + continue; + } + + // check HTTP method requirement + if ($requiredMethods = $route->getMethods()) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!in_array($method, $requiredMethods)) { + $this->allow = array_merge($this->allow, $requiredMethods); + + continue; + } + } + + $status = $this->handleRouteRequirements($pathinfo, $name, $route); + + if (self::ROUTE_MATCH === $status[0]) { + return $status[1]; + } + + if (self::REQUIREMENT_MISMATCH === $status[0]) { + continue; + } + + return $this->getAttributes($route, $name, array_replace($matches, $hostMatches)); + } + } + + /** + * Returns an array of values to use as request attributes. + * + * As this method requires the Route object, it is not available + * in matchers that do not have access to the matched Route instance + * (like the PHP and Apache matcher dumpers). + * + * @param Route $route The route we are matching against + * @param string $name The name of the route + * @param array $attributes An array of attributes from the matcher + * + * @return array An array of parameters + */ + protected function getAttributes(Route $route, $name, array $attributes) + { + $attributes['_route'] = $name; + + return $this->mergeDefaults($attributes, $route->getDefaults()); + } + + /** + * Handles specific route requirements. + * + * @param string $pathinfo The path + * @param string $name The route name + * @param Route $route The route + * + * @return array The first element represents the status, the second contains additional information + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + + // check HTTP scheme requirement + $scheme = $this->context->getScheme(); + $status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH; + + return array($status, null); + } + + /** + * Get merged default parameters. + * + * @param array $params The parameters + * @param array $defaults The defaults + * + * @return array Merged default parameters + */ + protected function mergeDefaults($params, $defaults) + { + foreach ($params as $key => $value) { + if (!is_int($key)) { + $defaults[$key] = $value; + } + } + + return $defaults; + } + + protected function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } +} diff --git a/vendor/symfony/routing/Matcher/UrlMatcherInterface.php b/vendor/symfony/routing/Matcher/UrlMatcherInterface.php new file mode 100644 index 00000000..af38662f --- /dev/null +++ b/vendor/symfony/routing/Matcher/UrlMatcherInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * UrlMatcherInterface is the interface that all URL matcher classes must implement. + * + * @author Fabien Potencier + */ +interface UrlMatcherInterface extends RequestContextAwareInterface +{ + /** + * Tries to match a URL path with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded) + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + public function match($pathinfo); +} diff --git a/vendor/symfony/routing/README.md b/vendor/symfony/routing/README.md new file mode 100644 index 00000000..88fb1fde --- /dev/null +++ b/vendor/symfony/routing/README.md @@ -0,0 +1,13 @@ +Routing Component +================= + +The Routing component maps an HTTP request to a set of configuration variables. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/routing/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/routing/RequestContext.php b/vendor/symfony/routing/RequestContext.php new file mode 100644 index 00000000..9b15cd07 --- /dev/null +++ b/vendor/symfony/routing/RequestContext.php @@ -0,0 +1,344 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Holds information about the current request. + * + * This class implements a fluent interface. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RequestContext +{ + private $baseUrl; + private $pathInfo; + private $method; + private $host; + private $scheme; + private $httpPort; + private $httpsPort; + private $queryString; + + /** + * @var array + */ + private $parameters = array(); + + /** + * Constructor. + * + * @param string $baseUrl The base URL + * @param string $method The HTTP method + * @param string $host The HTTP host name + * @param string $scheme The HTTP scheme + * @param int $httpPort The HTTP port + * @param int $httpsPort The HTTPS port + * @param string $path The path + * @param string $queryString The query string + */ + public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443, $path = '/', $queryString = '') + { + $this->setBaseUrl($baseUrl); + $this->setMethod($method); + $this->setHost($host); + $this->setScheme($scheme); + $this->setHttpPort($httpPort); + $this->setHttpsPort($httpsPort); + $this->setPathInfo($path); + $this->setQueryString($queryString); + } + + /** + * Updates the RequestContext information based on a HttpFoundation Request. + * + * @param Request $request A Request instance + * + * @return $this + */ + public function fromRequest(Request $request) + { + $this->setBaseUrl($request->getBaseUrl()); + $this->setPathInfo($request->getPathInfo()); + $this->setMethod($request->getMethod()); + $this->setHost($request->getHost()); + $this->setScheme($request->getScheme()); + $this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort()); + $this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort); + $this->setQueryString($request->server->get('QUERY_STRING', '')); + + return $this; + } + + /** + * Gets the base URL. + * + * @return string The base URL + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Sets the base URL. + * + * @param string $baseUrl The base URL + * + * @return $this + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = $baseUrl; + + return $this; + } + + /** + * Gets the path info. + * + * @return string The path info + */ + public function getPathInfo() + { + return $this->pathInfo; + } + + /** + * Sets the path info. + * + * @param string $pathInfo The path info + * + * @return $this + */ + public function setPathInfo($pathInfo) + { + $this->pathInfo = $pathInfo; + + return $this; + } + + /** + * Gets the HTTP method. + * + * The method is always an uppercased string. + * + * @return string The HTTP method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Sets the HTTP method. + * + * @param string $method The HTTP method + * + * @return $this + */ + public function setMethod($method) + { + $this->method = strtoupper($method); + + return $this; + } + + /** + * Gets the HTTP host. + * + * The host is always lowercased because it must be treated case-insensitive. + * + * @return string The HTTP host + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the HTTP host. + * + * @param string $host The HTTP host + * + * @return $this + */ + public function setHost($host) + { + $this->host = strtolower($host); + + return $this; + } + + /** + * Gets the HTTP scheme. + * + * @return string The HTTP scheme + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Sets the HTTP scheme. + * + * @param string $scheme The HTTP scheme + * + * @return $this + */ + public function setScheme($scheme) + { + $this->scheme = strtolower($scheme); + + return $this; + } + + /** + * Gets the HTTP port. + * + * @return int The HTTP port + */ + public function getHttpPort() + { + return $this->httpPort; + } + + /** + * Sets the HTTP port. + * + * @param int $httpPort The HTTP port + * + * @return $this + */ + public function setHttpPort($httpPort) + { + $this->httpPort = (int) $httpPort; + + return $this; + } + + /** + * Gets the HTTPS port. + * + * @return int The HTTPS port + */ + public function getHttpsPort() + { + return $this->httpsPort; + } + + /** + * Sets the HTTPS port. + * + * @param int $httpsPort The HTTPS port + * + * @return $this + */ + public function setHttpsPort($httpsPort) + { + $this->httpsPort = (int) $httpsPort; + + return $this; + } + + /** + * Gets the query string. + * + * @return string The query string without the "?" + */ + public function getQueryString() + { + return $this->queryString; + } + + /** + * Sets the query string. + * + * @param string $queryString The query string (after "?") + * + * @return $this + */ + public function setQueryString($queryString) + { + // string cast to be fault-tolerant, accepting null + $this->queryString = (string) $queryString; + + return $this; + } + + /** + * Returns the parameters. + * + * @return array The parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Sets the parameters. + * + * @param array $parameters The parameters + * + * @return $this + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * Gets a parameter value. + * + * @param string $name A parameter name + * + * @return mixed The parameter value or null if nonexistent + */ + public function getParameter($name) + { + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + } + + /** + * Checks if a parameter value is set for the given parameter. + * + * @param string $name A parameter name + * + * @return bool True if the parameter value is set, false otherwise + */ + public function hasParameter($name) + { + return array_key_exists($name, $this->parameters); + } + + /** + * Sets a parameter value. + * + * @param string $name A parameter name + * @param mixed $parameter The parameter value + * + * @return $this + */ + public function setParameter($name, $parameter) + { + $this->parameters[$name] = $parameter; + + return $this; + } +} diff --git a/vendor/symfony/routing/RequestContextAwareInterface.php b/vendor/symfony/routing/RequestContextAwareInterface.php new file mode 100644 index 00000000..ebb0ef46 --- /dev/null +++ b/vendor/symfony/routing/RequestContextAwareInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +interface RequestContextAwareInterface +{ + /** + * Sets the request context. + * + * @param RequestContext $context The context + */ + public function setContext(RequestContext $context); + + /** + * Gets the request context. + * + * @return RequestContext The context + */ + public function getContext(); +} diff --git a/vendor/symfony/routing/Route.php b/vendor/symfony/routing/Route.php new file mode 100644 index 00000000..34b1b757 --- /dev/null +++ b/vendor/symfony/routing/Route.php @@ -0,0 +1,593 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * A Route describes a route and its parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class Route implements \Serializable +{ + /** + * @var string + */ + private $path = '/'; + + /** + * @var string + */ + private $host = ''; + + /** + * @var array + */ + private $schemes = array(); + + /** + * @var array + */ + private $methods = array(); + + /** + * @var array + */ + private $defaults = array(); + + /** + * @var array + */ + private $requirements = array(); + + /** + * @var array + */ + private $options = array(); + + /** + * @var null|CompiledRoute + */ + private $compiled; + + /** + * @var string + */ + private $condition = ''; + + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * * utf8: Whether UTF-8 matching is enforced ot not + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string $host The host pattern to match + * @param string|array $schemes A required URI scheme or an array of restricted schemes + * @param string|array $methods A required HTTP method or an array of restricted methods + * @param string $condition A condition that should evaluate to true for the route to match + */ + public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '') + { + $this->setPath($path); + $this->setDefaults($defaults); + $this->setRequirements($requirements); + $this->setOptions($options); + $this->setHost($host); + $this->setSchemes($schemes); + $this->setMethods($methods); + $this->setCondition($condition); + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + 'path' => $this->path, + 'host' => $this->host, + 'defaults' => $this->defaults, + 'requirements' => $this->requirements, + 'options' => $this->options, + 'schemes' => $this->schemes, + 'methods' => $this->methods, + 'condition' => $this->condition, + 'compiled' => $this->compiled, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + if (\PHP_VERSION_ID >= 70000) { + $data = unserialize($serialized, array('allowed_classes' => array(CompiledRoute::class))); + } else { + $data = unserialize($serialized); + } + $this->path = $data['path']; + $this->host = $data['host']; + $this->defaults = $data['defaults']; + $this->requirements = $data['requirements']; + $this->options = $data['options']; + $this->schemes = $data['schemes']; + $this->methods = $data['methods']; + + if (isset($data['condition'])) { + $this->condition = $data['condition']; + } + if (isset($data['compiled'])) { + $this->compiled = $data['compiled']; + } + } + + /** + * Returns the pattern for the path. + * + * @return string The path pattern + */ + public function getPath() + { + return $this->path; + } + + /** + * Sets the pattern for the path. + * + * This method implements a fluent interface. + * + * @param string $pattern The path pattern + * + * @return $this + */ + public function setPath($pattern) + { + // A pattern must start with a slash and must not have multiple slashes at the beginning because the + // generated path for this route would be confused with a network path, e.g. '//domain.com/path'. + $this->path = '/'.ltrim(trim($pattern), '/'); + $this->compiled = null; + + return $this; + } + + /** + * Returns the pattern for the host. + * + * @return string The host pattern + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the pattern for the host. + * + * This method implements a fluent interface. + * + * @param string $pattern The host pattern + * + * @return $this + */ + public function setHost($pattern) + { + $this->host = (string) $pattern; + $this->compiled = null; + + return $this; + } + + /** + * Returns the lowercased schemes this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @return array The schemes + */ + public function getSchemes() + { + return $this->schemes; + } + + /** + * Sets the schemes (e.g. 'https') this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * This method implements a fluent interface. + * + * @param string|array $schemes The scheme or an array of schemes + * + * @return $this + */ + public function setSchemes($schemes) + { + $this->schemes = array_map('strtolower', (array) $schemes); + $this->compiled = null; + + return $this; + } + + /** + * Checks if a scheme requirement has been set. + * + * @param string $scheme + * + * @return bool true if the scheme requirement exists, otherwise false + */ + public function hasScheme($scheme) + { + return in_array(strtolower($scheme), $this->schemes, true); + } + + /** + * Returns the uppercased HTTP methods this route is restricted to. + * So an empty array means that any method is allowed. + * + * @return array The methods + */ + public function getMethods() + { + return $this->methods; + } + + /** + * Sets the HTTP methods (e.g. 'POST') this route is restricted to. + * So an empty array means that any method is allowed. + * + * This method implements a fluent interface. + * + * @param string|array $methods The method or an array of methods + * + * @return $this + */ + public function setMethods($methods) + { + $this->methods = array_map('strtoupper', (array) $methods); + $this->compiled = null; + + return $this; + } + + /** + * Returns the options. + * + * @return array The options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function setOptions(array $options) + { + $this->options = array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ); + + return $this->addOptions($options); + } + + /** + * Adds options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function addOptions(array $options) + { + foreach ($options as $name => $option) { + $this->options[$name] = $option; + } + $this->compiled = null; + + return $this; + } + + /** + * Sets an option value. + * + * This method implements a fluent interface. + * + * @param string $name An option name + * @param mixed $value The option value + * + * @return $this + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + $this->compiled = null; + + return $this; + } + + /** + * Get an option value. + * + * @param string $name An option name + * + * @return mixed The option value or null when not given + */ + public function getOption($name) + { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * Checks if an option has been set. + * + * @param string $name An option name + * + * @return bool true if the option is set, false otherwise + */ + public function hasOption($name) + { + return array_key_exists($name, $this->options); + } + + /** + * Returns the defaults. + * + * @return array The defaults + */ + public function getDefaults() + { + return $this->defaults; + } + + /** + * Sets the defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function setDefaults(array $defaults) + { + $this->defaults = array(); + + return $this->addDefaults($defaults); + } + + /** + * Adds defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function addDefaults(array $defaults) + { + foreach ($defaults as $name => $default) { + $this->defaults[$name] = $default; + } + $this->compiled = null; + + return $this; + } + + /** + * Gets a default value. + * + * @param string $name A variable name + * + * @return mixed The default value or null when not given + */ + public function getDefault($name) + { + return isset($this->defaults[$name]) ? $this->defaults[$name] : null; + } + + /** + * Checks if a default value is set for the given variable. + * + * @param string $name A variable name + * + * @return bool true if the default value is set, false otherwise + */ + public function hasDefault($name) + { + return array_key_exists($name, $this->defaults); + } + + /** + * Sets a default value. + * + * @param string $name A variable name + * @param mixed $default The default value + * + * @return $this + */ + public function setDefault($name, $default) + { + $this->defaults[$name] = $default; + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirements. + * + * @return array The requirements + */ + public function getRequirements() + { + return $this->requirements; + } + + /** + * Sets the requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function setRequirements(array $requirements) + { + $this->requirements = array(); + + return $this->addRequirements($requirements); + } + + /** + * Adds requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function addRequirements(array $requirements) + { + foreach ($requirements as $key => $regex) { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + } + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirement for the given key. + * + * @param string $key The key + * + * @return string|null The regex or null when not given + */ + public function getRequirement($key) + { + return isset($this->requirements[$key]) ? $this->requirements[$key] : null; + } + + /** + * Checks if a requirement is set for the given key. + * + * @param string $key A variable name + * + * @return bool true if a requirement is specified, false otherwise + */ + public function hasRequirement($key) + { + return array_key_exists($key, $this->requirements); + } + + /** + * Sets a requirement for the given key. + * + * @param string $key The key + * @param string $regex The regex + * + * @return $this + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + $this->compiled = null; + + return $this; + } + + /** + * Returns the condition. + * + * @return string The condition + */ + public function getCondition() + { + return $this->condition; + } + + /** + * Sets the condition. + * + * This method implements a fluent interface. + * + * @param string $condition The condition + * + * @return $this + */ + public function setCondition($condition) + { + $this->condition = (string) $condition; + $this->compiled = null; + + return $this; + } + + /** + * Compiles the route. + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + * + * @see RouteCompiler which is responsible for the compilation process + */ + public function compile() + { + if (null !== $this->compiled) { + return $this->compiled; + } + + $class = $this->getOption('compiler_class'); + + return $this->compiled = $class::compile($this); + } + + private function sanitizeRequirement($key, $regex) + { + if (!is_string($regex)) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key)); + } + + if ('' !== $regex && '^' === $regex[0]) { + $regex = (string) substr($regex, 1); // returns false for a single character + } + + if ('$' === substr($regex, -1)) { + $regex = substr($regex, 0, -1); + } + + if ('' === $regex) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key)); + } + + return $regex; + } +} diff --git a/vendor/symfony/routing/RouteCollection.php b/vendor/symfony/routing/RouteCollection.php new file mode 100644 index 00000000..2ccb90f3 --- /dev/null +++ b/vendor/symfony/routing/RouteCollection.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * A RouteCollection represents a set of Route instances. + * + * When adding a route at the end of the collection, an existing route + * with the same name is removed first. So there can only be one route + * with a given name. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCollection implements \IteratorAggregate, \Countable +{ + /** + * @var Route[] + */ + private $routes = array(); + + /** + * @var array + */ + private $resources = array(); + + public function __clone() + { + foreach ($this->routes as $name => $route) { + $this->routes[$name] = clone $route; + } + } + + /** + * Gets the current RouteCollection as an Iterator that includes all routes. + * + * It implements \IteratorAggregate. + * + * @see all() + * + * @return \ArrayIterator|Route[] An \ArrayIterator object for iterating over routes + */ + public function getIterator() + { + return new \ArrayIterator($this->routes); + } + + /** + * Gets the number of Routes in this collection. + * + * @return int The number of routes + */ + public function count() + { + return count($this->routes); + } + + /** + * Adds a route. + * + * @param string $name The route name + * @param Route $route A Route instance + */ + public function add($name, Route $route) + { + unset($this->routes[$name]); + + $this->routes[$name] = $route; + } + + /** + * Returns all routes in this collection. + * + * @return Route[] An array of routes + */ + public function all() + { + return $this->routes; + } + + /** + * Gets a route by name. + * + * @param string $name The route name + * + * @return Route|null A Route instance or null when not found + */ + public function get($name) + { + return isset($this->routes[$name]) ? $this->routes[$name] : null; + } + + /** + * Removes a route or an array of routes by name from the collection. + * + * @param string|array $name The route name or an array of route names + */ + public function remove($name) + { + foreach ((array) $name as $n) { + unset($this->routes[$n]); + } + } + + /** + * Adds a route collection at the end of the current set by appending all + * routes of the added collection. + * + * @param RouteCollection $collection A RouteCollection instance + */ + public function addCollection(RouteCollection $collection) + { + // we need to remove all routes with the same names first because just replacing them + // would not place the new route at the end of the merged array + foreach ($collection->all() as $name => $route) { + unset($this->routes[$name]); + $this->routes[$name] = $route; + } + + $this->resources = array_merge($this->resources, $collection->getResources()); + } + + /** + * Adds a prefix to the path of all child routes. + * + * @param string $prefix An optional prefix to add before each pattern of the route collection + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + */ + public function addPrefix($prefix, array $defaults = array(), array $requirements = array()) + { + $prefix = trim(trim($prefix), '/'); + + if ('' === $prefix) { + return; + } + + foreach ($this->routes as $route) { + $route->setPath('/'.$prefix.$route->getPath()); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets the host pattern on all routes. + * + * @param string $pattern The pattern + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + */ + public function setHost($pattern, array $defaults = array(), array $requirements = array()) + { + foreach ($this->routes as $route) { + $route->setHost($pattern); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets a condition on all routes. + * + * Existing conditions will be overridden. + * + * @param string $condition The condition + */ + public function setCondition($condition) + { + foreach ($this->routes as $route) { + $route->setCondition($condition); + } + } + + /** + * Adds defaults to all routes. + * + * An existing default value under the same name in a route will be overridden. + * + * @param array $defaults An array of default values + */ + public function addDefaults(array $defaults) + { + if ($defaults) { + foreach ($this->routes as $route) { + $route->addDefaults($defaults); + } + } + } + + /** + * Adds requirements to all routes. + * + * An existing requirement under the same name in a route will be overridden. + * + * @param array $requirements An array of requirements + */ + public function addRequirements(array $requirements) + { + if ($requirements) { + foreach ($this->routes as $route) { + $route->addRequirements($requirements); + } + } + } + + /** + * Adds options to all routes. + * + * An existing option value under the same name in a route will be overridden. + * + * @param array $options An array of options + */ + public function addOptions(array $options) + { + if ($options) { + foreach ($this->routes as $route) { + $route->addOptions($options); + } + } + } + + /** + * Sets the schemes (e.g. 'https') all child routes are restricted to. + * + * @param string|array $schemes The scheme or an array of schemes + */ + public function setSchemes($schemes) + { + foreach ($this->routes as $route) { + $route->setSchemes($schemes); + } + } + + /** + * Sets the HTTP methods (e.g. 'POST') all child routes are restricted to. + * + * @param string|array $methods The method or an array of methods + */ + public function setMethods($methods) + { + foreach ($this->routes as $route) { + $route->setMethods($methods); + } + } + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] An array of resources + */ + public function getResources() + { + return array_unique($this->resources); + } + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource A resource instance + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + } +} diff --git a/vendor/symfony/routing/RouteCollectionBuilder.php b/vendor/symfony/routing/RouteCollectionBuilder.php new file mode 100644 index 00000000..54bd86b7 --- /dev/null +++ b/vendor/symfony/routing/RouteCollectionBuilder.php @@ -0,0 +1,383 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Exception\FileLoaderLoadException; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Helps add and import routes into a RouteCollection. + * + * @author Ryan Weaver + */ +class RouteCollectionBuilder +{ + /** + * @var Route[]|RouteCollectionBuilder[] + */ + private $routes = array(); + + private $loader; + private $defaults = array(); + private $prefix; + private $host; + private $condition; + private $requirements = array(); + private $options = array(); + private $schemes; + private $methods; + private $resources = array(); + + /** + * @param LoaderInterface $loader + */ + public function __construct(LoaderInterface $loader = null) + { + $this->loader = $loader; + } + + /** + * Import an external routing resource and returns the RouteCollectionBuilder. + * + * $routes->import('blog.yml', '/blog'); + * + * @param mixed $resource + * @param string|null $prefix + * @param string $type + * + * @return self + * + * @throws FileLoaderLoadException + */ + public function import($resource, $prefix = '/', $type = null) + { + /** @var RouteCollection[] $collection */ + $collections = $this->load($resource, $type); + + // create a builder from the RouteCollection + $builder = $this->createBuilder(); + + foreach ($collections as $collection) { + if (null === $collection) { + continue; + } + + foreach ($collection->all() as $name => $route) { + $builder->addRoute($route, $name); + } + + foreach ($collection->getResources() as $resource) { + $builder->addResource($resource); + } + + // mount into this builder + $this->mount($prefix, $builder); + } + + return $builder; + } + + /** + * Adds a route and returns it for future modification. + * + * @param string $path The route path + * @param string $controller The route's controller + * @param string|null $name The name to give this route + * + * @return Route + */ + public function add($path, $controller, $name = null) + { + $route = new Route($path); + $route->setDefault('_controller', $controller); + $this->addRoute($route, $name); + + return $route; + } + + /** + * Returns a RouteCollectionBuilder that can be configured and then added with mount(). + * + * @return self + */ + public function createBuilder() + { + return new self($this->loader); + } + + /** + * Add a RouteCollectionBuilder. + * + * @param string $prefix + * @param RouteCollectionBuilder $builder + */ + public function mount($prefix, RouteCollectionBuilder $builder) + { + $builder->prefix = trim(trim($prefix), '/'); + $this->routes[] = $builder; + } + + /** + * Adds a Route object to the builder. + * + * @param Route $route + * @param string|null $name + * + * @return $this + */ + public function addRoute(Route $route, $name = null) + { + if (null === $name) { + // used as a flag to know which routes will need a name later + $name = '_unnamed_route_'.spl_object_hash($route); + } + + $this->routes[$name] = $route; + + return $this; + } + + /** + * Sets the host on all embedded routes (unless already set). + * + * @param string $pattern + * + * @return $this + */ + public function setHost($pattern) + { + $this->host = $pattern; + + return $this; + } + + /** + * Sets a condition on all embedded routes (unless already set). + * + * @param string $condition + * + * @return $this + */ + public function setCondition($condition) + { + $this->condition = $condition; + + return $this; + } + + /** + * Sets a default value that will be added to all embedded routes (unless that + * default value is already set). + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function setDefault($key, $value) + { + $this->defaults[$key] = $value; + + return $this; + } + + /** + * Sets a requirement that will be added to all embedded routes (unless that + * requirement is already set). + * + * @param string $key + * @param mixed $regex + * + * @return $this + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $regex; + + return $this; + } + + /** + * Sets an option that will be added to all embedded routes (unless that + * option is already set). + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function setOption($key, $value) + { + $this->options[$key] = $value; + + return $this; + } + + /** + * Sets the schemes on all embedded routes (unless already set). + * + * @param array|string $schemes + * + * @return $this + */ + public function setSchemes($schemes) + { + $this->schemes = $schemes; + + return $this; + } + + /** + * Sets the methods on all embedded routes (unless already set). + * + * @param array|string $methods + * + * @return $this + */ + public function setMethods($methods) + { + $this->methods = $methods; + + return $this; + } + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource + * + * @return $this + */ + private function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + + return $this; + } + + /** + * Creates the final RouteCollection and returns it. + * + * @return RouteCollection + */ + public function build() + { + $routeCollection = new RouteCollection(); + + foreach ($this->routes as $name => $route) { + if ($route instanceof Route) { + $route->setDefaults(array_merge($this->defaults, $route->getDefaults())); + $route->setOptions(array_merge($this->options, $route->getOptions())); + + foreach ($this->requirements as $key => $val) { + if (!$route->hasRequirement($key)) { + $route->setRequirement($key, $val); + } + } + + if (null !== $this->prefix) { + $route->setPath('/'.$this->prefix.$route->getPath()); + } + + if (!$route->getHost()) { + $route->setHost($this->host); + } + + if (!$route->getCondition()) { + $route->setCondition($this->condition); + } + + if (!$route->getSchemes()) { + $route->setSchemes($this->schemes); + } + + if (!$route->getMethods()) { + $route->setMethods($this->methods); + } + + // auto-generate the route name if it's been marked + if ('_unnamed_route_' === substr($name, 0, 15)) { + $name = $this->generateRouteName($route); + } + + $routeCollection->add($name, $route); + } else { + /* @var self $route */ + $subCollection = $route->build(); + $subCollection->addPrefix($this->prefix); + + $routeCollection->addCollection($subCollection); + } + + foreach ($this->resources as $resource) { + $routeCollection->addResource($resource); + } + } + + return $routeCollection; + } + + /** + * Generates a route name based on details of this route. + * + * @return string + */ + private function generateRouteName(Route $route) + { + $methods = implode('_', $route->getMethods()).'_'; + + $routeName = $methods.$route->getPath(); + $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); + $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); + + // Collapse consecutive underscores down into a single underscore. + $routeName = preg_replace('/_+/', '_', $routeName); + + return $routeName; + } + + /** + * Finds a loader able to load an imported resource and loads it. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return RouteCollection[] + * + * @throws FileLoaderLoadException If no loader is found + */ + private function load($resource, $type = null) + { + if (null === $this->loader) { + throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.'); + } + + if ($this->loader->supports($resource, $type)) { + $collections = $this->loader->load($resource, $type); + + return is_array($collections) ? $collections : array($collections); + } + + if (null === $resolver = $this->loader->getResolver()) { + throw new FileLoaderLoadException($resource, null, null, null, $type); + } + + if (false === $loader = $resolver->resolve($resource, $type)) { + throw new FileLoaderLoadException($resource, null, null, null, $type); + } + + $collections = $loader->load($resource, $type); + + return is_array($collections) ? $collections : array($collections); + } +} diff --git a/vendor/symfony/routing/RouteCompiler.php b/vendor/symfony/routing/RouteCompiler.php new file mode 100644 index 00000000..a64776a0 --- /dev/null +++ b/vendor/symfony/routing/RouteCompiler.php @@ -0,0 +1,319 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompiler compiles Route instances to CompiledRoute instances. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCompiler implements RouteCompilerInterface +{ + const REGEX_DELIMITER = '#'; + + /** + * This string defines the characters that are automatically considered separators in front of + * optional placeholders (with default and no static text following). Such a single separator + * can be left out together with the optional placeholder from matching and generating URLs. + */ + const SEPARATORS = '/,;.:-_~+*=@|'; + + /** + * The maximum supported length of a PCRE subpattern name + * http://pcre.org/current/doc/html/pcre2pattern.html#SEC16. + * + * @internal + */ + const VARIABLE_MAXIMUM_LENGTH = 32; + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException If a path variable is named _fragment + * @throws \LogicException If a variable is referenced more than once + * @throws \DomainException If a variable name starts with a digit or if it is too long to be successfully used as + * a PCRE subpattern. + */ + public static function compile(Route $route) + { + $hostVariables = array(); + $variables = array(); + $hostRegex = null; + $hostTokens = array(); + + if ('' !== $host = $route->getHost()) { + $result = self::compilePattern($route, $host, true); + + $hostVariables = $result['variables']; + $variables = $hostVariables; + + $hostTokens = $result['tokens']; + $hostRegex = $result['regex']; + } + + $path = $route->getPath(); + + $result = self::compilePattern($route, $path, false); + + $staticPrefix = $result['staticPrefix']; + + $pathVariables = $result['variables']; + + foreach ($pathVariables as $pathParam) { + if ('_fragment' === $pathParam) { + throw new \InvalidArgumentException(sprintf('Route pattern "%s" cannot contain "_fragment" as a path parameter.', $route->getPath())); + } + } + + $variables = array_merge($variables, $pathVariables); + + $tokens = $result['tokens']; + $regex = $result['regex']; + + return new CompiledRoute( + $staticPrefix, + $regex, + $tokens, + $pathVariables, + $hostRegex, + $hostTokens, + $hostVariables, + array_unique($variables) + ); + } + + private static function compilePattern(Route $route, $pattern, $isHost) + { + $tokens = array(); + $variables = array(); + $matches = array(); + $pos = 0; + $defaultSeparator = $isHost ? '.' : '/'; + $useUtf8 = preg_match('//u', $pattern); + $needsUtf8 = $route->getOption('utf8'); + + if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) { + $needsUtf8 = true; + @trigger_error(sprintf('Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "%s".', $pattern), E_USER_DEPRECATED); + } + if (!$useUtf8 && $needsUtf8) { + throw new \LogicException(sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern)); + } + + // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable + // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. + preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + foreach ($matches as $match) { + $varName = substr($match[0][0], 1, -1); + // get all static text preceding the current variable + $precedingText = substr($pattern, $pos, $match[0][1] - $pos); + $pos = $match[0][1] + strlen($match[0][0]); + + if (!strlen($precedingText)) { + $precedingChar = ''; + } elseif ($useUtf8) { + preg_match('/.$/u', $precedingText, $precedingChar); + $precedingChar = $precedingChar[0]; + } else { + $precedingChar = substr($precedingText, -1); + } + $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); + + // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the + // variable would not be usable as a Controller action argument. + if (preg_match('/^\d/', $varName)) { + throw new \DomainException(sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern)); + } + if (in_array($varName, $variables)) { + throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName)); + } + + if (strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) { + throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %s characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern)); + } + + if ($isSeparator && $precedingText !== $precedingChar) { + $tokens[] = array('text', substr($precedingText, 0, -strlen($precedingChar))); + } elseif (!$isSeparator && strlen($precedingText) > 0) { + $tokens[] = array('text', $precedingText); + } + + $regexp = $route->getRequirement($varName); + if (null === $regexp) { + $followingPattern = (string) substr($pattern, $pos); + // Find the next static character after the variable that functions as a separator. By default, this separator and '/' + // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all + // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are + // the same that will be matched. Example: new Route('/{page}.{_format}', array('_format' => 'html')) + // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything. + // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally + // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. + $nextSeparator = self::findNextSeparator($followingPattern, $useUtf8); + $regexp = sprintf( + '[^%s%s]+', + preg_quote($defaultSeparator, self::REGEX_DELIMITER), + $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '' + ); + if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) { + // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive + // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. + // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow + // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is + // directly adjacent, e.g. '/{x}{y}'. + $regexp .= '+'; + } + } else { + if (!preg_match('//u', $regexp)) { + $useUtf8 = false; + } elseif (!$needsUtf8 && preg_match('/[\x80-\xFF]|(?= 0; --$i) { + $token = $tokens[$i]; + if ('variable' === $token[0] && $route->hasDefault($token[3])) { + $firstOptional = $i; + } else { + break; + } + } + } + + // compute the matching regexp + $regexp = ''; + for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { + $regexp .= self::computeRegexp($tokens, $i, $firstOptional); + } + $regexp = self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s'.($isHost ? 'i' : ''); + + // enable Utf8 matching if really required + if ($needsUtf8) { + $regexp .= 'u'; + for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { + if ('variable' === $tokens[$i][0]) { + $tokens[$i][] = true; + } + } + } + + return array( + 'staticPrefix' => self::determineStaticPrefix($route, $tokens), + 'regex' => $regexp, + 'tokens' => array_reverse($tokens), + 'variables' => $variables, + ); + } + + /** + * Determines the longest static prefix possible for a route. + * + * @param Route $route + * @param array $tokens + * + * @return string The leading static part of a route's path + */ + private static function determineStaticPrefix(Route $route, array $tokens) + { + if ('text' !== $tokens[0][0]) { + return ($route->hasDefault($tokens[0][3]) || '/' === $tokens[0][1]) ? '' : $tokens[0][1]; + } + + $prefix = $tokens[0][1]; + + if (isset($tokens[1][1]) && '/' !== $tokens[1][1] && false === $route->hasDefault($tokens[1][3])) { + $prefix .= $tokens[1][1]; + } + + return $prefix; + } + + /** + * Returns the next static character in the Route pattern that will serve as a separator. + * + * @param string $pattern The route pattern + * @param bool $useUtf8 Whether the character is encoded in UTF-8 or not + * + * @return string The next static character that functions as separator (or empty string when none available) + */ + private static function findNextSeparator($pattern, $useUtf8) + { + if ('' == $pattern) { + // return empty string if pattern is empty or false (false which can be returned by substr) + return ''; + } + // first remove all placeholders from the pattern so we can find the next real static character + if ('' === $pattern = preg_replace('#\{\w+\}#', '', $pattern)) { + return ''; + } + if ($useUtf8) { + preg_match('/^./u', $pattern, $pattern); + } + + return false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; + } + + /** + * Computes the regexp used to match a specific token. It can be static text or a subpattern. + * + * @param array $tokens The route tokens + * @param int $index The index of the current token + * @param int $firstOptional The index of the first optional token + * + * @return string The regexp pattern for a single token + */ + private static function computeRegexp(array $tokens, $index, $firstOptional) + { + $token = $tokens[$index]; + if ('text' === $token[0]) { + // Text tokens + return preg_quote($token[1], self::REGEX_DELIMITER); + } else { + // Variable tokens + if (0 === $index && 0 === $firstOptional) { + // When the only token is an optional variable token, the separator is required + return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + } else { + $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + if ($index >= $firstOptional) { + // Enclose each optional token in a subpattern to make it optional. + // "?:" means it is non-capturing, i.e. the portion of the subject string that + // matched the optional subpattern is not passed back. + $regexp = "(?:$regexp"; + $nbTokens = count($tokens); + if ($nbTokens - 1 == $index) { + // Close the optional subpatterns + $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); + } + } + + return $regexp; + } + } + } +} diff --git a/vendor/symfony/routing/RouteCompilerInterface.php b/vendor/symfony/routing/RouteCompilerInterface.php new file mode 100644 index 00000000..e6f8ee6d --- /dev/null +++ b/vendor/symfony/routing/RouteCompilerInterface.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\Routing; + +/** + * RouteCompilerInterface is the interface that all RouteCompiler classes must implement. + * + * @author Fabien Potencier + */ +interface RouteCompilerInterface +{ + /** + * Compiles the current route instance. + * + * @param Route $route A Route instance + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + */ + public static function compile(Route $route); +} diff --git a/vendor/symfony/routing/Router.php b/vendor/symfony/routing/Router.php new file mode 100644 index 00000000..6ac4205a --- /dev/null +++ b/vendor/symfony/routing/Router.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\Routing; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\ConfigCacheInterface; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheFactory; +use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * The Router class is an example of the integration of all pieces of the + * routing system for easier use. + * + * @author Fabien Potencier + */ +class Router implements RouterInterface, RequestMatcherInterface +{ + /** + * @var UrlMatcherInterface|null + */ + protected $matcher; + + /** + * @var UrlGeneratorInterface|null + */ + protected $generator; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var LoaderInterface + */ + protected $loader; + + /** + * @var RouteCollection|null + */ + protected $collection; + + /** + * @var mixed + */ + protected $resource; + + /** + * @var array + */ + protected $options = array(); + + /** + * @var LoggerInterface|null + */ + protected $logger; + + /** + * @var ConfigCacheFactoryInterface|null + */ + private $configCacheFactory; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + /** + * Constructor. + * + * @param LoaderInterface $loader A LoaderInterface instance + * @param mixed $resource The main resource to load + * @param array $options An array of options + * @param RequestContext $context The context + * @param LoggerInterface $logger A logger instance + */ + public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, LoggerInterface $logger = null) + { + $this->loader = $loader; + $this->resource = $resource; + $this->logger = $logger; + $this->context = $context ?: new RequestContext(); + $this->setOptions($options); + } + + /** + * Sets options. + * + * Available options: + * + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * * generator_class: The name of a UrlGeneratorInterface implementation + * * generator_base_class: The base class for the dumped generator class + * * generator_cache_class: The class name for the dumped generator class + * * generator_dumper_class: The name of a GeneratorDumperInterface implementation + * * matcher_class: The name of a UrlMatcherInterface implementation + * * matcher_base_class: The base class for the dumped matcher class + * * matcher_dumper_class: The class name for the dumped matcher class + * * matcher_cache_class: The name of a MatcherDumperInterface implementation + * * resource_type: Type hint for the main resource (optional) + * * strict_requirements: Configure strict requirement checking for generators + * implementing ConfigurableRequirementsInterface (default is true) + * + * @param array $options An array of options + * + * @throws \InvalidArgumentException When unsupported option is provided + */ + public function setOptions(array $options) + { + $this->options = array( + 'cache_dir' => null, + 'debug' => false, + 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper', + 'generator_cache_class' => 'ProjectUrlGenerator', + 'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', + 'matcher_cache_class' => 'ProjectUrlMatcher', + 'resource_type' => null, + 'strict_requirements' => true, + ); + + // check option names and live merge, if errors are encountered Exception will be thrown + $invalid = array(); + foreach ($options as $key => $value) { + if (array_key_exists($key, $this->options)) { + $this->options[$key] = $value; + } else { + $invalid[] = $key; + } + } + + if ($invalid) { + throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('", "', $invalid))); + } + } + + /** + * Sets an option. + * + * @param string $key The key + * @param mixed $value The value + * + * @throws \InvalidArgumentException + */ + public function setOption($key, $value) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + $this->options[$key] = $value; + } + + /** + * Gets an option value. + * + * @param string $key The key + * + * @return mixed The value + * + * @throws \InvalidArgumentException + */ + public function getOption($key) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + return $this->options[$key]; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollection() + { + if (null === $this->collection) { + $this->collection = $this->loader->load($this->resource, $this->options['resource_type']); + } + + return $this->collection; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + + if (null !== $this->matcher) { + $this->getMatcher()->setContext($context); + } + if (null !== $this->generator) { + $this->getGenerator()->setContext($context); + } + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * Sets the ConfigCache factory to use. + * + * @param ConfigCacheFactoryInterface $configCacheFactory The factory to use + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + { + $this->configCacheFactory = $configCacheFactory; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + return $this->getMatcher()->match($pathinfo); + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + $matcher = $this->getMatcher(); + if (!$matcher instanceof RequestMatcherInterface) { + // fallback to the default UrlMatcherInterface + return $matcher->match($request->getPathInfo()); + } + + return $matcher->matchRequest($request); + } + + /** + * Gets the UrlMatcher instance associated with this Router. + * + * @return UrlMatcherInterface A UrlMatcherInterface instance + */ + public function getMatcher() + { + if (null !== $this->matcher) { + return $this->matcher; + } + + if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) { + $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context); + if (method_exists($this->matcher, 'addExpressionLanguageProvider')) { + foreach ($this->expressionLanguageProviders as $provider) { + $this->matcher->addExpressionLanguageProvider($provider); + } + } + + return $this->matcher; + } + + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['matcher_cache_class'].'.php', + function (ConfigCacheInterface $cache) { + $dumper = $this->getMatcherDumperInstance(); + if (method_exists($dumper, 'addExpressionLanguageProvider')) { + foreach ($this->expressionLanguageProviders as $provider) { + $dumper->addExpressionLanguageProvider($provider); + } + } + + $options = array( + 'class' => $this->options['matcher_cache_class'], + 'base_class' => $this->options['matcher_base_class'], + ); + + $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + } + ); + + require_once $cache->getPath(); + + return $this->matcher = new $this->options['matcher_cache_class']($this->context); + } + + /** + * Gets the UrlGenerator instance associated with this Router. + * + * @return UrlGeneratorInterface A UrlGeneratorInterface instance + */ + public function getGenerator() + { + if (null !== $this->generator) { + return $this->generator; + } + + if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) { + $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger); + } else { + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['generator_cache_class'].'.php', + function (ConfigCacheInterface $cache) { + $dumper = $this->getGeneratorDumperInstance(); + + $options = array( + 'class' => $this->options['generator_cache_class'], + 'base_class' => $this->options['generator_base_class'], + ); + + $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + } + ); + + require_once $cache->getPath(); + + $this->generator = new $this->options['generator_cache_class']($this->context, $this->logger); + } + + if ($this->generator instanceof ConfigurableRequirementsInterface) { + $this->generator->setStrictRequirements($this->options['strict_requirements']); + } + + return $this->generator; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * @return GeneratorDumperInterface + */ + protected function getGeneratorDumperInstance() + { + return new $this->options['generator_dumper_class']($this->getRouteCollection()); + } + + /** + * @return MatcherDumperInterface + */ + protected function getMatcherDumperInstance() + { + return new $this->options['matcher_dumper_class']($this->getRouteCollection()); + } + + /** + * Provides the ConfigCache factory implementation, falling back to a + * default implementation if necessary. + * + * @return ConfigCacheFactoryInterface $configCacheFactory + */ + private function getConfigCacheFactory() + { + if (null === $this->configCacheFactory) { + $this->configCacheFactory = new ConfigCacheFactory($this->options['debug']); + } + + return $this->configCacheFactory; + } +} diff --git a/vendor/symfony/routing/RouterInterface.php b/vendor/symfony/routing/RouterInterface.php new file mode 100644 index 00000000..a10ae34e --- /dev/null +++ b/vendor/symfony/routing/RouterInterface.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\Routing; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * RouterInterface is the interface that all Router classes must implement. + * + * This interface is the concatenation of UrlMatcherInterface and UrlGeneratorInterface. + * + * @author Fabien Potencier + */ +interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface +{ + /** + * Gets the RouteCollection instance associated with this Router. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRouteCollection(); +} diff --git a/vendor/symfony/routing/Tests/Annotation/RouteTest.php b/vendor/symfony/routing/Tests/Annotation/RouteTest.php new file mode 100644 index 00000000..9af22f29 --- /dev/null +++ b/vendor/symfony/routing/Tests/Annotation/RouteTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Annotation; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Annotation\Route; + +class RouteTest extends TestCase +{ + /** + * @expectedException \BadMethodCallException + */ + public function testInvalidRouteParameter() + { + $route = new Route(array('foo' => 'bar')); + } + + /** + * @dataProvider getValidParameters + */ + public function testRouteParameters($parameter, $value, $getter) + { + $route = new Route(array($parameter => $value)); + $this->assertEquals($route->$getter(), $value); + } + + public function getValidParameters() + { + return array( + array('value', '/Blog', 'getPath'), + array('requirements', array('locale' => 'en'), 'getRequirements'), + array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'), + array('name', 'blog_index', 'getName'), + array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'), + array('schemes', array('https'), 'getSchemes'), + array('methods', array('GET', 'POST'), 'getMethods'), + array('host', '{locale}.example.com', 'getHost'), + array('condition', 'context.getMethod() == "GET"', 'getCondition'), + ); + } +} diff --git a/vendor/symfony/routing/Tests/CompiledRouteTest.php b/vendor/symfony/routing/Tests/CompiledRouteTest.php new file mode 100644 index 00000000..c5531795 --- /dev/null +++ b/vendor/symfony/routing/Tests/CompiledRouteTest.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\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\CompiledRoute; + +class CompiledRouteTest extends TestCase +{ + public function testAccessors() + { + $compiled = new CompiledRoute('prefix', 'regex', array('tokens'), array(), array(), array(), array(), array('variables')); + $this->assertEquals('prefix', $compiled->getStaticPrefix(), '__construct() takes a static prefix as its second argument'); + $this->assertEquals('regex', $compiled->getRegex(), '__construct() takes a regexp as its third argument'); + $this->assertEquals(array('tokens'), $compiled->getTokens(), '__construct() takes an array of tokens as its fourth argument'); + $this->assertEquals(array('variables'), $compiled->getVariables(), '__construct() takes an array of variables as its ninth argument'); + } +} diff --git a/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php b/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.php new file mode 100644 index 00000000..97a34c96 --- /dev/null +++ b/vendor/symfony/routing/Tests/DependencyInjection/RoutingResolverPassTest.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\Routing\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; + +class RoutingResolverPassTest extends TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('routing.resolver', LoaderResolver::class); + $container->register('loader1')->addTag('routing.loader'); + $container->register('loader2')->addTag('routing.loader'); + + (new RoutingResolverPass())->process($container); + + $this->assertEquals( + array(array('addLoader', array(new Reference('loader1'))), array('addLoader', array(new Reference('loader2')))), + $container->getDefinition('routing.resolver')->getMethodCalls() + ); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php new file mode 100644 index 00000000..56bcab2a --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +abstract class AbstractClass +{ +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php new file mode 100644 index 00000000..a3882773 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class BarClass +{ + public function routeAction($arg1, $arg2 = 'defaultValue2', $arg3 = 'defaultValue3') + { + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php new file mode 100644 index 00000000..471968b5 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BazClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class BazClass +{ + public function __invoke() + { + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php new file mode 100644 index 00000000..320dc350 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooClass.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class FooClass +{ +} diff --git a/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php new file mode 100644 index 00000000..ee8f4b07 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Config\Util\XmlUtils; + +/** + * XmlFileLoader with schema validation turned off. + */ +class CustomXmlFileLoader extends XmlFileLoader +{ + protected function loadFile($file) + { + return XmlUtils::loadFile($file, function () { return true; }); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php b/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php new file mode 100644 index 00000000..8900d34e --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php @@ -0,0 +1,3 @@ +class NoStartTagClass +{ +} diff --git a/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php b/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php new file mode 100644 index 00000000..729c9b4d --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\OtherAnnotatedClasses; + +class VariadicClass +{ + public function routeAction(...$params) + { + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php b/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php new file mode 100644 index 00000000..15937bcf --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.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\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; + +/** + * @author Fabien Potencier + */ +class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + public function redirect($path, $route, $scheme = null) + { + return array( + '_controller' => 'Some controller reference...', + 'path' => $path, + 'scheme' => $scheme, + ); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/annotated.php b/vendor/symfony/routing/Tests/Fixtures/annotated.php new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/bad_format.yml b/vendor/symfony/routing/Tests/Fixtures/bad_format.yml new file mode 100644 index 00000000..8ba50e2e --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/bad_format.yml @@ -0,0 +1,3 @@ +blog_show: + path: /blog/{slug} + defaults: { _controller: "MyBundle:Blog:show" } diff --git a/vendor/symfony/routing/Tests/Fixtures/bar.xml b/vendor/symfony/routing/Tests/Fixtures/bar.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml b/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml new file mode 100644 index 00000000..d0788366 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes1.yml @@ -0,0 +1,2 @@ +route1: + path: /route/1 diff --git a/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml b/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml new file mode 100644 index 00000000..938fb245 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/directory/recurse/routes2.yml @@ -0,0 +1,2 @@ +route2: + path: /route/2 diff --git a/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml b/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml new file mode 100644 index 00000000..088cfb4d --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/directory/routes3.yml @@ -0,0 +1,2 @@ +route3: + path: /route/3 diff --git a/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml b/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml new file mode 100644 index 00000000..af829e58 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/directory_import/import.yml @@ -0,0 +1,3 @@ +_directory: + resource: "../directory" + type: directory diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.apache b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.apache new file mode 100644 index 00000000..26a561cc --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.apache @@ -0,0 +1,163 @@ +# skip "real" requests +RewriteCond %{REQUEST_FILENAME} -f +RewriteRule .* - [QSA,L] + +# foo +RewriteCond %{REQUEST_URI} ^/foo/(baz|symfony)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:foo,E=_ROUTING_param_bar:%1,E=_ROUTING_default_def:test] + +# foobar +RewriteCond %{REQUEST_URI} ^/foo(?:/([^/]++))?$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:foobar,E=_ROUTING_param_bar:%1,E=_ROUTING_default_bar:toto] + +# bar +RewriteCond %{REQUEST_URI} ^/bar/([^/]++)$ +RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] +RewriteRule .* - [S=1,E=_ROUTING_allow_GET:1,E=_ROUTING_allow_HEAD:1] +RewriteCond %{REQUEST_URI} ^/bar/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:bar,E=_ROUTING_param_foo:%1] + +# baragain +RewriteCond %{REQUEST_URI} ^/baragain/([^/]++)$ +RewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$ [NC] +RewriteRule .* - [S=1,E=_ROUTING_allow_GET:1,E=_ROUTING_allow_POST:1,E=_ROUTING_allow_HEAD:1] +RewriteCond %{REQUEST_URI} ^/baragain/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baragain,E=_ROUTING_param_foo:%1] + +# baz +RewriteCond %{REQUEST_URI} ^/test/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz] + +# baz2 +RewriteCond %{REQUEST_URI} ^/test/baz\.html$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz2] + +# baz3 +RewriteCond %{REQUEST_URI} ^/test/baz3$ +RewriteRule .* $0/ [QSA,L,R=301] +RewriteCond %{REQUEST_URI} ^/test/baz3/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz3] + +# baz4 +RewriteCond %{REQUEST_URI} ^/test/([^/]++)$ +RewriteRule .* $0/ [QSA,L,R=301] +RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz4,E=_ROUTING_param_foo:%1] + +# baz5 +RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$ +RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] +RewriteRule .* - [S=2,E=_ROUTING_allow_GET:1,E=_ROUTING_allow_HEAD:1] +RewriteCond %{REQUEST_URI} ^/test/([^/]++)$ +RewriteRule .* $0/ [QSA,L,R=301] +RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz5,E=_ROUTING_param_foo:%1] + +# baz5unsafe +RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]++)/$ +RewriteCond %{REQUEST_METHOD} !^(POST)$ [NC] +RewriteRule .* - [S=1,E=_ROUTING_allow_POST:1] +RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]++)/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz5unsafe,E=_ROUTING_param_foo:%1] + +# baz6 +RewriteCond %{REQUEST_URI} ^/test/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz6,E=_ROUTING_default_foo:bar\ baz] + +# baz7 +RewriteCond %{REQUEST_URI} ^/te\ st/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz7] + +# baz8 +RewriteCond %{REQUEST_URI} ^/te\\\ st/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz8] + +# baz9 +RewriteCond %{REQUEST_URI} ^/test/(te\\\ st)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz9,E=_ROUTING_param_baz:%1] + +RewriteCond %{HTTP:Host} ^a\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_1:1] + +# route1 +RewriteCond %{ENV:__ROUTING_host_1} =1 +RewriteCond %{REQUEST_URI} ^/route1$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route1] + +# route2 +RewriteCond %{ENV:__ROUTING_host_1} =1 +RewriteCond %{REQUEST_URI} ^/c2/route2$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route2] + +RewriteCond %{HTTP:Host} ^b\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_2:1] + +# route3 +RewriteCond %{ENV:__ROUTING_host_2} =1 +RewriteCond %{REQUEST_URI} ^/c2/route3$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route3] + +RewriteCond %{HTTP:Host} ^a\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_3:1] + +# route4 +RewriteCond %{ENV:__ROUTING_host_3} =1 +RewriteCond %{REQUEST_URI} ^/route4$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route4] + +RewriteCond %{HTTP:Host} ^c\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_4:1] + +# route5 +RewriteCond %{ENV:__ROUTING_host_4} =1 +RewriteCond %{REQUEST_URI} ^/route5$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route5] + +# route6 +RewriteCond %{REQUEST_URI} ^/route6$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route6] + +RewriteCond %{HTTP:Host} ^([^\.]++)\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_5:1,E=__ROUTING_host_5_var1:%1] + +# route11 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route11$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route11,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1}] + +# route12 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route12$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route12,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1},E=_ROUTING_default_var1:val] + +# route13 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route13/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route13,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1},E=_ROUTING_param_name:%1] + +# route14 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route14/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route14,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1},E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val] + +RewriteCond %{HTTP:Host} ^c\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_6:1] + +# route15 +RewriteCond %{ENV:__ROUTING_host_6} =1 +RewriteCond %{REQUEST_URI} ^/route15/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route15,E=_ROUTING_param_name:%1] + +# route16 +RewriteCond %{REQUEST_URI} ^/route16/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route16,E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val] + +# route17 +RewriteCond %{REQUEST_URI} ^/route17$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route17] + +# 405 Method Not Allowed +RewriteCond %{ENV:_ROUTING__allow_GET} =1 [OR] +RewriteCond %{ENV:_ROUTING__allow_HEAD} =1 [OR] +RewriteCond %{ENV:_ROUTING__allow_POST} =1 +RewriteRule .* app.php [QSA,L] diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php new file mode 100644 index 00000000..8ae0ee96 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php @@ -0,0 +1,317 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/foo')) { + // foo + if (preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + // foofoo + if ('/foofoo' === $pathinfo) { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + } + + elseif (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_bar; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + } + not_bar: + + // barhead + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_barhead; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + } + not_barhead: + + } + + elseif (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ('/test/baz' === $pathinfo) { + return array('_route' => 'baz'); + } + + // baz2 + if ('/test/baz.html' === $pathinfo) { + return array('_route' => 'baz2'); + } + + // baz3 + if ('/test/baz3/' === $pathinfo) { + return array('_route' => 'baz3'); + } + + } + + // baz4 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); + } + + // baz5 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_baz5; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + } + not_baz5: + + // baz.baz6 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('PUT' !== $canonicalMethod) { + $allow[] = 'PUT'; + goto not_bazbaz6; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + } + not_bazbaz6: + + } + + // quoter + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ('/spa ce' === $pathinfo) { + return array('_route' => 'space'); + } + + if (0 === strpos($pathinfo, '/a')) { + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); + } + + // bar1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); + } + + } + + // overridden + if (preg_match('#^/a/(?P.*)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); + } + + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); + } + + // bar2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); + } + + } + + } + + elseif (0 === strpos($pathinfo, '/multi')) { + // helloWorld + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); + } + + // hey + if ('/multi/hey/' === $pathinfo) { + return array('_route' => 'hey'); + } + + // overridden2 + if ('/multi/new' === $pathinfo) { + return array('_route' => 'overridden2'); + } + + } + + // foo3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); + } + + // bar3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); + } + + if (0 === strpos($pathinfo, '/aba')) { + // ababa + if ('/ababa' === $pathinfo) { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $context->getHost(); + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route1 + if ('/route1' === $pathinfo) { + return array('_route' => 'route1'); + } + + // route2 + if ('/c2/route2' === $pathinfo) { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) { + // route3 + if ('/c2/route3' === $pathinfo) { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route4 + if ('/route4' === $pathinfo) { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route5 + if ('/route5' === $pathinfo) { + return array('_route' => 'route5'); + } + + } + + // route6 + if ('/route6' === $pathinfo) { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ('/route11' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ('/route12' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); + } + + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); + } + + } + + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); + } + + // route17 + if ('/route17' === $pathinfo) { + return array('_route' => 'route17'); + } + + // a + if ('/a/a...' === $pathinfo) { + return array('_route' => 'a'); + } + + if (0 === strpos($pathinfo, '/a/b')) { + // b + if (preg_match('#^/a/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); + } + + // c + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); + } + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.apache b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.apache new file mode 100644 index 00000000..309f2ff0 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.apache @@ -0,0 +1,7 @@ +# skip "real" requests +RewriteCond %{REQUEST_FILENAME} -f +RewriteRule .* - [QSA,L] + +# foo +RewriteCond %{REQUEST_URI} ^/foo$ +RewriteRule .* ap\ p_d\ ev.php [QSA,L,E=_ROUTING_route:foo] diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php new file mode 100644 index 00000000..91ec4789 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php @@ -0,0 +1,349 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/foo')) { + // foo + if (preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + // foofoo + if ('/foofoo' === $pathinfo) { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + } + + elseif (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_bar; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + } + not_bar: + + // barhead + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_barhead; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + } + not_barhead: + + } + + elseif (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ('/test/baz' === $pathinfo) { + return array('_route' => 'baz'); + } + + // baz2 + if ('/test/baz.html' === $pathinfo) { + return array('_route' => 'baz2'); + } + + // baz3 + if ('/test/baz3' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'baz3'); + } + + return array('_route' => 'baz3'); + } + + } + + // baz4 + if (preg_match('#^/test/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'baz4'); + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); + } + + // baz5 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_baz5; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + } + not_baz5: + + // baz.baz6 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('PUT' !== $canonicalMethod) { + $allow[] = 'PUT'; + goto not_bazbaz6; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + } + not_bazbaz6: + + } + + // quoter + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ('/spa ce' === $pathinfo) { + return array('_route' => 'space'); + } + + if (0 === strpos($pathinfo, '/a')) { + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); + } + + // bar1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); + } + + } + + // overridden + if (preg_match('#^/a/(?P.*)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); + } + + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); + } + + // bar2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); + } + + } + + } + + elseif (0 === strpos($pathinfo, '/multi')) { + // helloWorld + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); + } + + // hey + if ('/multi/hey' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'hey'); + } + + return array('_route' => 'hey'); + } + + // overridden2 + if ('/multi/new' === $pathinfo) { + return array('_route' => 'overridden2'); + } + + } + + // foo3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); + } + + // bar3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); + } + + if (0 === strpos($pathinfo, '/aba')) { + // ababa + if ('/ababa' === $pathinfo) { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $context->getHost(); + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route1 + if ('/route1' === $pathinfo) { + return array('_route' => 'route1'); + } + + // route2 + if ('/c2/route2' === $pathinfo) { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) { + // route3 + if ('/c2/route3' === $pathinfo) { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route4 + if ('/route4' === $pathinfo) { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route5 + if ('/route5' === $pathinfo) { + return array('_route' => 'route5'); + } + + } + + // route6 + if ('/route6' === $pathinfo) { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ('/route11' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ('/route12' === $pathinfo) { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); + } + + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); + } + + } + + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); + } + + // route17 + if ('/route17' === $pathinfo) { + return array('_route' => 'route17'); + } + + // a + if ('/a/a...' === $pathinfo) { + return array('_route' => 'a'); + } + + if (0 === strpos($pathinfo, '/a/b')) { + // b + if (preg_match('#^/a/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); + } + + // c + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); + } + + } + + // secure + if ('/secure' === $pathinfo) { + $requiredSchemes = array ( 'https' => 0,); + if (!isset($requiredSchemes[$scheme])) { + return $this->redirect($pathinfo, 'secure', key($requiredSchemes)); + } + + return array('_route' => 'secure'); + } + + // nonsecure + if ('/nonsecure' === $pathinfo) { + $requiredSchemes = array ( 'http' => 0,); + if (!isset($requiredSchemes[$scheme])) { + return $this->redirect($pathinfo, 'nonsecure', key($requiredSchemes)); + } + + return array('_route' => 'nonsecure'); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php new file mode 100644 index 00000000..48cd6dfa --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php @@ -0,0 +1,58 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/rootprefix')) { + // static + if ('/rootprefix/test' === $pathinfo) { + return array('_route' => 'static'); + } + + // dynamic + if (preg_match('#^/rootprefix/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'dynamic')), array ()); + } + + } + + // with-condition + if ('/with-condition' === $pathinfo && ($context->getMethod() == "GET")) { + return array('_route' => 'with-condition'); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php new file mode 100644 index 00000000..b90e49af --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher4.php @@ -0,0 +1,98 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + // just_head + if ('/just_head' === $pathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_just_head; + } + + return array('_route' => 'just_head'); + } + not_just_head: + + // head_and_get + if ('/head_and_get' === $pathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_head_and_get; + } + + return array('_route' => 'head_and_get'); + } + not_head_and_get: + + // post_and_head + if ('/post_and_get' === $pathinfo) { + if (!in_array($requestMethod, array('POST', 'HEAD'))) { + $allow = array_merge($allow, array('POST', 'HEAD')); + goto not_post_and_head; + } + + return array('_route' => 'post_and_head'); + } + not_post_and_head: + + if (0 === strpos($pathinfo, '/put_and_post')) { + // put_and_post + if ('/put_and_post' === $pathinfo) { + if (!in_array($requestMethod, array('PUT', 'POST'))) { + $allow = array_merge($allow, array('PUT', 'POST')); + goto not_put_and_post; + } + + return array('_route' => 'put_and_post'); + } + not_put_and_post: + + // put_and_get_and_head + if ('/put_and_post' === $pathinfo) { + if (!in_array($canonicalMethod, array('PUT', 'GET'))) { + $allow = array_merge($allow, array('PUT', 'GET')); + goto not_put_and_get_and_head; + } + + return array('_route' => 'put_and_get_and_head'); + } + not_put_and_get_and_head: + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php new file mode 100644 index 00000000..1e6824b3 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher5.php @@ -0,0 +1,158 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/a')) { + // a_first + if ('/a/11' === $pathinfo) { + return array('_route' => 'a_first'); + } + + // a_second + if ('/a/22' === $pathinfo) { + return array('_route' => 'a_second'); + } + + // a_third + if ('/a/333' === $pathinfo) { + return array('_route' => 'a_third'); + } + + } + + // a_wildcard + if (preg_match('#^/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'a_wildcard')), array ()); + } + + if (0 === strpos($pathinfo, '/a')) { + // a_fourth + if ('/a/44' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'a_fourth'); + } + + return array('_route' => 'a_fourth'); + } + + // a_fifth + if ('/a/55' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'a_fifth'); + } + + return array('_route' => 'a_fifth'); + } + + // a_sixth + if ('/a/66' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'a_sixth'); + } + + return array('_route' => 'a_sixth'); + } + + } + + // nested_wildcard + if (0 === strpos($pathinfo, '/nested') && preg_match('#^/nested/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'nested_wildcard')), array ()); + } + + if (0 === strpos($pathinfo, '/nested/group')) { + // nested_a + if ('/nested/group/a' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'nested_a'); + } + + return array('_route' => 'nested_a'); + } + + // nested_b + if ('/nested/group/b' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'nested_b'); + } + + return array('_route' => 'nested_b'); + } + + // nested_c + if ('/nested/group/c' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'nested_c'); + } + + return array('_route' => 'nested_c'); + } + + } + + elseif (0 === strpos($pathinfo, '/slashed/group')) { + // slashed_a + if ('/slashed/group' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'slashed_a'); + } + + return array('_route' => 'slashed_a'); + } + + // slashed_b + if ('/slashed/group/b' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'slashed_b'); + } + + return array('_route' => 'slashed_b'); + } + + // slashed_c + if ('/slashed/group/c' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'slashed_c'); + } + + return array('_route' => 'slashed_c'); + } + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php new file mode 100644 index 00000000..c7645521 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher6.php @@ -0,0 +1,204 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/trailing/simple')) { + // simple_trailing_slash_no_methods + if ('/trailing/simple/no-methods/' === $pathinfo) { + return array('_route' => 'simple_trailing_slash_no_methods'); + } + + // simple_trailing_slash_GET_method + if ('/trailing/simple/get-method/' === $pathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_simple_trailing_slash_GET_method; + } + + return array('_route' => 'simple_trailing_slash_GET_method'); + } + not_simple_trailing_slash_GET_method: + + // simple_trailing_slash_HEAD_method + if ('/trailing/simple/head-method/' === $pathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_simple_trailing_slash_HEAD_method; + } + + return array('_route' => 'simple_trailing_slash_HEAD_method'); + } + not_simple_trailing_slash_HEAD_method: + + // simple_trailing_slash_POST_method + if ('/trailing/simple/post-method/' === $pathinfo) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_simple_trailing_slash_POST_method; + } + + return array('_route' => 'simple_trailing_slash_POST_method'); + } + not_simple_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/trailing/regex')) { + // regex_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ()); + } + + // regex_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_regex_trailing_slash_GET_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); + } + not_regex_trailing_slash_GET_method: + + // regex_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_regex_trailing_slash_HEAD_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ()); + } + not_regex_trailing_slash_HEAD_method: + + // regex_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_regex_trailing_slash_POST_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + } + not_regex_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/simple')) { + // simple_not_trailing_slash_no_methods + if ('/not-trailing/simple/no-methods' === $pathinfo) { + return array('_route' => 'simple_not_trailing_slash_no_methods'); + } + + // simple_not_trailing_slash_GET_method + if ('/not-trailing/simple/get-method' === $pathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_simple_not_trailing_slash_GET_method; + } + + return array('_route' => 'simple_not_trailing_slash_GET_method'); + } + not_simple_not_trailing_slash_GET_method: + + // simple_not_trailing_slash_HEAD_method + if ('/not-trailing/simple/head-method' === $pathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_simple_not_trailing_slash_HEAD_method; + } + + return array('_route' => 'simple_not_trailing_slash_HEAD_method'); + } + not_simple_not_trailing_slash_HEAD_method: + + // simple_not_trailing_slash_POST_method + if ('/not-trailing/simple/post-method' === $pathinfo) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_simple_not_trailing_slash_POST_method; + } + + return array('_route' => 'simple_not_trailing_slash_POST_method'); + } + not_simple_not_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/regex')) { + // regex_not_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ()); + } + + // regex_not_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_regex_not_trailing_slash_GET_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + } + not_regex_not_trailing_slash_GET_method: + + // regex_not_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_regex_not_trailing_slash_HEAD_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + } + not_regex_not_trailing_slash_HEAD_method: + + // regex_not_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_regex_not_trailing_slash_POST_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + } + not_regex_not_trailing_slash_POST_method: + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php new file mode 100644 index 00000000..876a90f2 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher7.php @@ -0,0 +1,228 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $trimmedPathinfo = rtrim($pathinfo, '/'); + $context = $this->context; + $request = $this->request; + $requestMethod = $canonicalMethod = $context->getMethod(); + $scheme = $context->getScheme(); + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + + + if (0 === strpos($pathinfo, '/trailing/simple')) { + // simple_trailing_slash_no_methods + if ('/trailing/simple/no-methods' === $trimmedPathinfo) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'simple_trailing_slash_no_methods'); + } + + return array('_route' => 'simple_trailing_slash_no_methods'); + } + + // simple_trailing_slash_GET_method + if ('/trailing/simple/get-method' === $trimmedPathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_simple_trailing_slash_GET_method; + } + + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'simple_trailing_slash_GET_method'); + } + + return array('_route' => 'simple_trailing_slash_GET_method'); + } + not_simple_trailing_slash_GET_method: + + // simple_trailing_slash_HEAD_method + if ('/trailing/simple/head-method' === $trimmedPathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_simple_trailing_slash_HEAD_method; + } + + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'simple_trailing_slash_HEAD_method'); + } + + return array('_route' => 'simple_trailing_slash_HEAD_method'); + } + not_simple_trailing_slash_HEAD_method: + + // simple_trailing_slash_POST_method + if ('/trailing/simple/post-method/' === $pathinfo) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_simple_trailing_slash_POST_method; + } + + return array('_route' => 'simple_trailing_slash_POST_method'); + } + not_simple_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/trailing/regex')) { + // regex_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/trailing/regex/no-methods') && preg_match('#^/trailing/regex/no\\-methods/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'regex_trailing_slash_no_methods'); + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_no_methods')), array ()); + } + + // regex_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/trailing/regex/get-method') && preg_match('#^/trailing/regex/get\\-method/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_regex_trailing_slash_GET_method; + } + + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'regex_trailing_slash_GET_method'); + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_GET_method')), array ()); + } + not_regex_trailing_slash_GET_method: + + // regex_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/trailing/regex/head-method') && preg_match('#^/trailing/regex/head\\-method/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_regex_trailing_slash_HEAD_method; + } + + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'regex_trailing_slash_HEAD_method'); + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_HEAD_method')), array ()); + } + not_regex_trailing_slash_HEAD_method: + + // regex_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/trailing/regex/post-method') && preg_match('#^/trailing/regex/post\\-method/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_regex_trailing_slash_POST_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_trailing_slash_POST_method')), array ()); + } + not_regex_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/simple')) { + // simple_not_trailing_slash_no_methods + if ('/not-trailing/simple/no-methods' === $pathinfo) { + return array('_route' => 'simple_not_trailing_slash_no_methods'); + } + + // simple_not_trailing_slash_GET_method + if ('/not-trailing/simple/get-method' === $pathinfo) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_simple_not_trailing_slash_GET_method; + } + + return array('_route' => 'simple_not_trailing_slash_GET_method'); + } + not_simple_not_trailing_slash_GET_method: + + // simple_not_trailing_slash_HEAD_method + if ('/not-trailing/simple/head-method' === $pathinfo) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_simple_not_trailing_slash_HEAD_method; + } + + return array('_route' => 'simple_not_trailing_slash_HEAD_method'); + } + not_simple_not_trailing_slash_HEAD_method: + + // simple_not_trailing_slash_POST_method + if ('/not-trailing/simple/post-method' === $pathinfo) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_simple_not_trailing_slash_POST_method; + } + + return array('_route' => 'simple_not_trailing_slash_POST_method'); + } + not_simple_not_trailing_slash_POST_method: + + } + + elseif (0 === strpos($pathinfo, '/not-trailing/regex')) { + // regex_not_trailing_slash_no_methods + if (0 === strpos($pathinfo, '/not-trailing/regex/no-methods') && preg_match('#^/not\\-trailing/regex/no\\-methods/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_no_methods')), array ()); + } + + // regex_not_trailing_slash_GET_method + if (0 === strpos($pathinfo, '/not-trailing/regex/get-method') && preg_match('#^/not\\-trailing/regex/get\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('GET' !== $canonicalMethod) { + $allow[] = 'GET'; + goto not_regex_not_trailing_slash_GET_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_GET_method')), array ()); + } + not_regex_not_trailing_slash_GET_method: + + // regex_not_trailing_slash_HEAD_method + if (0 === strpos($pathinfo, '/not-trailing/regex/head-method') && preg_match('#^/not\\-trailing/regex/head\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('HEAD' !== $requestMethod) { + $allow[] = 'HEAD'; + goto not_regex_not_trailing_slash_HEAD_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_HEAD_method')), array ()); + } + not_regex_not_trailing_slash_HEAD_method: + + // regex_not_trailing_slash_POST_method + if (0 === strpos($pathinfo, '/not-trailing/regex/post-method') && preg_match('#^/not\\-trailing/regex/post\\-method/(?P[^/]++)$#s', $pathinfo, $matches)) { + if ('POST' !== $canonicalMethod) { + $allow[] = 'POST'; + goto not_regex_not_trailing_slash_POST_method; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'regex_not_trailing_slash_POST_method')), array ()); + } + not_regex_not_trailing_slash_POST_method: + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Tests/Fixtures/empty.yml b/vendor/symfony/routing/Tests/Fixtures/empty.yml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/file_resource.yml b/vendor/symfony/routing/Tests/Fixtures/file_resource.yml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/foo.xml b/vendor/symfony/routing/Tests/Fixtures/foo.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/foo1.xml b/vendor/symfony/routing/Tests/Fixtures/foo1.xml new file mode 100644 index 00000000..e69de29b diff --git a/vendor/symfony/routing/Tests/Fixtures/incomplete.yml b/vendor/symfony/routing/Tests/Fixtures/incomplete.yml new file mode 100644 index 00000000..df64d324 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/incomplete.yml @@ -0,0 +1,2 @@ +blog_show: + defaults: { _controller: MyBlogBundle:Blog:show } diff --git a/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml new file mode 100644 index 00000000..f93bf9c6 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/list_defaults.xml @@ -0,0 +1,20 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + true + 1 + 3.5 + foo + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml new file mode 100644 index 00000000..987086db --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/list_in_list_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml new file mode 100644 index 00000000..32d393c5 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/list_in_map_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml b/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml new file mode 100644 index 00000000..c70e03cc --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/list_null_values.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml new file mode 100644 index 00000000..47feb29b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/map_defaults.xml @@ -0,0 +1,20 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + true + 1 + 3.5 + foo + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml new file mode 100644 index 00000000..6d770653 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/map_in_list_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml new file mode 100644 index 00000000..2beee614 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/map_in_map_defaults.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + true + 1 + 3.5 + foo + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml b/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml new file mode 100644 index 00000000..8fd8954e --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/map_null_values.xml @@ -0,0 +1,22 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/missing_id.xml b/vendor/symfony/routing/Tests/Fixtures/missing_id.xml new file mode 100644 index 00000000..4ea4115f --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/missing_id.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/missing_path.xml b/vendor/symfony/routing/Tests/Fixtures/missing_path.xml new file mode 100644 index 00000000..ef5bc088 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/missing_path.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml b/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml new file mode 100644 index 00000000..e33955ae --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml @@ -0,0 +1,16 @@ + + + + + + MyBundle:Blog:show + \w+ + en|fr|de + RouteCompiler + + 1 + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml b/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml new file mode 100644 index 00000000..a3e94737 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonesense_resource_plus_path.yml @@ -0,0 +1,3 @@ +blog_show: + resource: validpattern.yml + path: /test diff --git a/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml b/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml new file mode 100644 index 00000000..547cda3b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonesense_type_without_resource.yml @@ -0,0 +1,3 @@ +blog_show: + path: /blog/{slug} + type: custom diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml b/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml new file mode 100644 index 00000000..dc147d2e --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalid.xml @@ -0,0 +1,10 @@ + + + + + + MyBundle:Blog:show + + diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml b/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml new file mode 100644 index 00000000..257cc564 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalid.yml @@ -0,0 +1 @@ +foo diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml b/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml new file mode 100644 index 00000000..cfa9992b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalid2.yml @@ -0,0 +1 @@ +route: string diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml b/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml new file mode 100644 index 00000000..015e270f --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalidkeys.yml @@ -0,0 +1,3 @@ +someroute: + resource: path/to/some.yml + name_prefix: test_ diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml b/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml new file mode 100644 index 00000000..863ef03b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalidnode.xml @@ -0,0 +1,8 @@ + + + + + bar + diff --git a/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml b/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml new file mode 100644 index 00000000..908958c0 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/nonvalidroute.xml @@ -0,0 +1,12 @@ + + + + + + MyBundle:Blog:show + + baz + + diff --git a/vendor/symfony/routing/Tests/Fixtures/null_values.xml b/vendor/symfony/routing/Tests/Fixtures/null_values.xml new file mode 100644 index 00000000..f9e2aa24 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/null_values.xml @@ -0,0 +1,12 @@ + + + + + + + foo + bar + + diff --git a/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml b/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml new file mode 100644 index 00000000..ecfde280 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/scalar_defaults.xml @@ -0,0 +1,33 @@ + + + + + + AcmeBlogBundle:Blog:index + + + + true + + + 1 + + + 3.5 + + + false + + + 1 + + + 0 + + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml b/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml new file mode 100644 index 00000000..78be239a --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/special_route_name.yml @@ -0,0 +1,2 @@ +"#$péß^a|": + path: "true" diff --git a/vendor/symfony/routing/Tests/Fixtures/validpattern.php b/vendor/symfony/routing/Tests/Fixtures/validpattern.php new file mode 100644 index 00000000..edc16d8c --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validpattern.php @@ -0,0 +1,18 @@ +add('blog_show', new Route( + '/blog/{slug}', + array('_controller' => 'MyBlogBundle:Blog:show'), + array('locale' => '\w+'), + array('compiler_class' => 'RouteCompiler'), + '{locale}.example.com', + array('https'), + array('GET', 'POST', 'put', 'OpTiOnS'), + 'context.getMethod() == "GET"' +)); + +return $collection; diff --git a/vendor/symfony/routing/Tests/Fixtures/validpattern.xml b/vendor/symfony/routing/Tests/Fixtures/validpattern.xml new file mode 100644 index 00000000..dbc72e46 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validpattern.xml @@ -0,0 +1,15 @@ + + + + + + MyBundle:Blog:show + \w+ + + context.getMethod() == "GET" + + + + diff --git a/vendor/symfony/routing/Tests/Fixtures/validpattern.yml b/vendor/symfony/routing/Tests/Fixtures/validpattern.yml new file mode 100644 index 00000000..565abaaa --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validpattern.yml @@ -0,0 +1,13 @@ +blog_show: + path: /blog/{slug} + defaults: { _controller: "MyBundle:Blog:show" } + host: "{locale}.example.com" + requirements: { 'locale': '\w+' } + methods: ['GET','POST','put','OpTiOnS'] + schemes: ['https'] + condition: 'context.getMethod() == "GET"' + options: + compiler_class: RouteCompiler + +blog_show_inherited: + path: /blog/{slug} diff --git a/vendor/symfony/routing/Tests/Fixtures/validresource.php b/vendor/symfony/routing/Tests/Fixtures/validresource.php new file mode 100644 index 00000000..482c80b2 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validresource.php @@ -0,0 +1,18 @@ +import('validpattern.php'); +$collection->addDefaults(array( + 'foo' => 123, +)); +$collection->addRequirements(array( + 'foo' => '\d+', +)); +$collection->addOptions(array( + 'foo' => 'bar', +)); +$collection->setCondition('context.getMethod() == "POST"'); +$collection->addPrefix('/prefix'); + +return $collection; diff --git a/vendor/symfony/routing/Tests/Fixtures/validresource.xml b/vendor/symfony/routing/Tests/Fixtures/validresource.xml new file mode 100644 index 00000000..b7a15ddc --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validresource.xml @@ -0,0 +1,13 @@ + + + + + + 123 + \d+ + + context.getMethod() == "POST" + + diff --git a/vendor/symfony/routing/Tests/Fixtures/validresource.yml b/vendor/symfony/routing/Tests/Fixtures/validresource.yml new file mode 100644 index 00000000..faf2263a --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/validresource.yml @@ -0,0 +1,8 @@ +_blog: + resource: validpattern.yml + prefix: /{foo} + defaults: { 'foo': '123' } + requirements: { 'foo': '\d+' } + options: { 'foo': 'bar' } + host: "" + condition: 'context.getMethod() == "POST"' diff --git a/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php b/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php new file mode 100644 index 00000000..5871420b --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/with_define_path_variable.php @@ -0,0 +1,5 @@ + + + diff --git a/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php new file mode 100644 index 00000000..f84802b3 --- /dev/null +++ b/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Generator\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper; +use Symfony\Component\Routing\RequestContext; + +class PhpGeneratorDumperTest extends TestCase +{ + /** + * @var RouteCollection + */ + private $routeCollection; + + /** + * @var PhpGeneratorDumper + */ + private $generatorDumper; + + /** + * @var string + */ + private $testTmpFilepath; + + /** + * @var string + */ + private $largeTestTmpFilepath; + + protected function setUp() + { + parent::setUp(); + + $this->routeCollection = new RouteCollection(); + $this->generatorDumper = new PhpGeneratorDumper($this->routeCollection); + $this->testTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.php'; + $this->largeTestTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.large.php'; + @unlink($this->testTmpFilepath); + @unlink($this->largeTestTmpFilepath); + } + + protected function tearDown() + { + parent::tearDown(); + + @unlink($this->testTmpFilepath); + + $this->routeCollection = null; + $this->generatorDumper = null; + $this->testTmpFilepath = null; + } + + public function testDumpWithRoutes() + { + $this->routeCollection->add('Test', new Route('/testing/{foo}')); + $this->routeCollection->add('Test2', new Route('/testing2')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump()); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \ProjectUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter); + $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter); + $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter); + $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter); + } + + public function testDumpWithTooManyRoutes() + { + if (defined('HHVM_VERSION_ID')) { + $this->markTestSkipped('HHVM consumes too much memory on this test.'); + } + + $this->routeCollection->add('Test', new Route('/testing/{foo}')); + for ($i = 0; $i < 32769; ++$i) { + $this->routeCollection->add('route_'.$i, new Route('/route_'.$i)); + } + $this->routeCollection->add('Test2', new Route('/testing2')); + + file_put_contents($this->largeTestTmpFilepath, $this->generatorDumper->dump(array( + 'class' => 'ProjectLargeUrlGenerator', + ))); + $this->routeCollection = $this->generatorDumper = null; + include $this->largeTestTmpFilepath; + + $projectUrlGenerator = new \ProjectLargeUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('http://localhost/app.php/testing/bar', $absoluteUrlWithParameter); + $this->assertEquals('http://localhost/app.php/testing2', $absoluteUrlWithoutParameter); + $this->assertEquals('/app.php/testing/bar', $relativeUrlWithParameter); + $this->assertEquals('/app.php/testing2', $relativeUrlWithoutParameter); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testDumpWithoutRoutes() + { + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'WithoutRoutesUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \WithoutRoutesUrlGenerator(new RequestContext('/app.php')); + + $projectUrlGenerator->generate('Test', array()); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGenerateNonExistingRoute() + { + $this->routeCollection->add('Test', new Route('/test')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'NonExistingRoutesUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \NonExistingRoutesUrlGenerator(new RequestContext()); + $url = $projectUrlGenerator->generate('NonExisting', array()); + } + + public function testDumpForRouteWithDefaults() + { + $this->routeCollection->add('Test', new Route('/testing/{foo}', array('foo' => 'bar'))); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'DefaultRoutesUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \DefaultRoutesUrlGenerator(new RequestContext()); + $url = $projectUrlGenerator->generate('Test', array()); + + $this->assertEquals('/testing', $url); + } + + public function testDumpWithSchemeRequirement() + { + $this->routeCollection->add('Test1', new Route('/testing', array(), array(), array(), '', array('ftp', 'https'))); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'SchemeUrlGenerator'))); + include $this->testTmpFilepath; + + $projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('ftp://localhost/app.php/testing', $absoluteUrl); + $this->assertEquals('ftp://localhost/app.php/testing', $relativeUrl); + + $projectUrlGenerator = new \SchemeUrlGenerator(new RequestContext('/app.php', 'GET', 'localhost', 'https')); + + $absoluteUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $relativeUrl = $projectUrlGenerator->generate('Test1', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('https://localhost/app.php/testing', $absoluteUrl); + $this->assertEquals('/app.php/testing', $relativeUrl); + } +} diff --git a/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php b/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php new file mode 100644 index 00000000..e334e437 --- /dev/null +++ b/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php @@ -0,0 +1,692 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Generator; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RequestContext; + +class UrlGeneratorTest extends TestCase +{ + public function testAbsoluteUrlWithPort80() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost/app.php/testing', $url); + } + + public function testAbsoluteSecureUrlWithPort443() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('https://localhost/app.php/testing', $url); + } + + public function testAbsoluteUrlWithNonStandardPort() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('httpPort' => 8080))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost:8080/app.php/testing', $url); + } + + public function testAbsoluteSecureUrlWithNonStandardPort() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('httpsPort' => 8080, 'scheme' => 'https'))->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('https://localhost:8080/app.php/testing', $url); + } + + public function testRelativeUrlWithoutParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing', $url); + } + + public function testRelativeUrlWithParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing/bar', $url); + } + + public function testRelativeUrlWithNullParameter() + { + $routes = $this->getRoutes('test', new Route('/testing.{format}', array('format' => null))); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing', $url); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testRelativeUrlWithNullParameterButNotOptional() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}/bar', array('foo' => null))); + // This must raise an exception because the default requirement for "foo" is "[^/]+" which is not met with these params. + // Generating path "/testing//bar" would be wrong as matching this route would fail. + $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + public function testRelativeUrlWithOptionalZeroParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{page}')); + $url = $this->getGenerator($routes)->generate('test', array('page' => 0), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing/0', $url); + } + + public function testNotPassedOptionalParameterInBetween() + { + $routes = $this->getRoutes('test', new Route('/{slug}/{page}', array('slug' => 'index', 'page' => 0))); + $this->assertSame('/app.php/index/1', $this->getGenerator($routes)->generate('test', array('page' => 1))); + $this->assertSame('/app.php/', $this->getGenerator($routes)->generate('test')); + } + + public function testRelativeUrlWithExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing?foo=bar', $url); + } + + public function testAbsoluteUrlWithExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost/app.php/testing?foo=bar', $url); + } + + public function testUrlWithNullExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => null), UrlGeneratorInterface::ABSOLUTE_URL); + + $this->assertEquals('http://localhost/app.php/testing', $url); + } + + public function testUrlWithExtraParametersFromGlobals() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('bar', 'bar'); + $generator->setContext($context); + $url = $generator->generate('test', array('foo' => 'bar')); + + $this->assertEquals('/app.php/testing?foo=bar', $url); + } + + public function testUrlWithGlobalParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('foo', 'bar'); + $generator->setContext($context); + $url = $generator->generate('test', array()); + + $this->assertEquals('/app.php/testing/bar', $url); + } + + public function testGlobalParameterHasHigherPriorityThanDefault() + { + $routes = $this->getRoutes('test', new Route('/{_locale}', array('_locale' => 'en'))); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('_locale', 'de'); + $generator->setContext($context); + $url = $generator->generate('test', array()); + + $this->assertSame('/app.php/de', $url); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGenerateWithoutRoutes() + { + $routes = $this->getRoutes('foo', new Route('/testing/{foo}')); + $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\MissingMandatoryParametersException + */ + public function testGenerateForRouteWithoutMandatoryParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidOptionalParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '1|2'))); + $this->getGenerator($routes)->generate('test', array('foo' => '0'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + public function testGenerateForRouteWithInvalidOptionalParameterNonStrict() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testGenerateForRouteWithInvalidOptionalParameterNonStrictWithLogger() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $logger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); + $logger->expects($this->once()) + ->method('error'); + $generator = $this->getGenerator($routes, array(), $logger); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function testGenerateForRouteWithInvalidParameterButDisabledRequirementsCheck() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(null); + $this->assertSame('/app.php/testing/bar', $generator->generate('test', array('foo' => 'bar'))); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidMandatoryParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => 'd+'))); + $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidUtf8Parameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '\pL+'), array('utf8' => true))); + $this->getGenerator($routes)->generate('test', array('foo' => 'abc123'), UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testRequiredParamAndEmptyPassed() + { + $routes = $this->getRoutes('test', new Route('/{slug}', array(), array('slug' => '.+'))); + $this->getGenerator($routes)->generate('test', array('slug' => '')); + } + + public function testSchemeRequirementDoesNothingIfSameCurrentScheme() + { + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http'))); + $this->assertEquals('/app.php/', $this->getGenerator($routes)->generate('test')); + + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https'))); + $this->assertEquals('/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test')); + } + + public function testSchemeRequirementForcesAbsoluteUrl() + { + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('https'))); + $this->assertEquals('https://localhost/app.php/', $this->getGenerator($routes)->generate('test')); + + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('http'))); + $this->assertEquals('http://localhost/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test')); + } + + public function testSchemeRequirementCreatesUrlForFirstRequiredScheme() + { + $routes = $this->getRoutes('test', new Route('/', array(), array(), array(), '', array('Ftp', 'https'))); + $this->assertEquals('ftp://localhost/app.php/', $this->getGenerator($routes)->generate('test')); + } + + public function testPathWithTwoStartingSlashes() + { + $routes = $this->getRoutes('test', new Route('//path-and-not-domain')); + + // this must not generate '//path-and-not-domain' because that would be a network path + $this->assertSame('/path-and-not-domain', $this->getGenerator($routes, array('BaseUrl' => ''))->generate('test')); + } + + public function testNoTrailingSlashForMultipleOptionalParameters() + { + $routes = $this->getRoutes('test', new Route('/category/{slug1}/{slug2}/{slug3}', array('slug2' => null, 'slug3' => null))); + + $this->assertEquals('/app.php/category/foo', $this->getGenerator($routes)->generate('test', array('slug1' => 'foo'))); + } + + public function testWithAnIntegerAsADefaultValue() + { + $routes = $this->getRoutes('test', new Route('/{default}', array('default' => 0))); + + $this->assertEquals('/app.php/foo', $this->getGenerator($routes)->generate('test', array('default' => 'foo'))); + } + + public function testNullForOptionalParameterIsIgnored() + { + $routes = $this->getRoutes('test', new Route('/test/{default}', array('default' => 0))); + + $this->assertEquals('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => null))); + } + + public function testQueryParamSameAsDefault() + { + $routes = $this->getRoutes('test', new Route('/test', array('page' => 1))); + + $this->assertSame('/app.php/test?page=2', $this->getGenerator($routes)->generate('test', array('page' => 2))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => 1))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('page' => '1'))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test')); + } + + public function testArrayQueryParamSameAsDefault() + { + $routes = $this->getRoutes('test', new Route('/test', array('array' => array('foo', 'bar')))); + + $this->assertSame('/app.php/test?array%5B0%5D=bar&array%5B1%5D=foo', $this->getGenerator($routes)->generate('test', array('array' => array('bar', 'foo')))); + $this->assertSame('/app.php/test?array%5Ba%5D=foo&array%5Bb%5D=bar', $this->getGenerator($routes)->generate('test', array('array' => array('a' => 'foo', 'b' => 'bar')))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array('foo', 'bar')))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('array' => array(1 => 'bar', 0 => 'foo')))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test')); + } + + public function testGenerateWithSpecialRouteName() + { + $routes = $this->getRoutes('$péß^a|', new Route('/bar')); + + $this->assertSame('/app.php/bar', $this->getGenerator($routes)->generate('$péß^a|')); + } + + public function testUrlEncoding() + { + $expectedPath = '/app.php/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + .'/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + .'?query=%40%3A%5B%5D/%28%29%2A%27%22%20%2B%2C%3B-._~%26%24%3C%3E%7C%7B%7D%25%5C%5E%60%21%3Ffoo%3Dbar%23id'; + + // This tests the encoding of reserved characters that are used for delimiting of URI components (defined in RFC 3986) + // and other special ASCII chars. These chars are tested as static text path, variable path and query param. + $chars = '@:[]/()*\'" +,;-._~&$<>|{}%\\^`!?foo=bar#id'; + $routes = $this->getRoutes('test', new Route("/$chars/{varpath}", array(), array('varpath' => '.+'))); + $this->assertSame($expectedPath, $this->getGenerator($routes)->generate('test', array( + 'varpath' => $chars, + 'query' => $chars, + ))); + } + + public function testEncodingOfRelativePathSegments() + { + $routes = $this->getRoutes('test', new Route('/dir/../dir/..')); + $this->assertSame('/app.php/dir/%2E%2E/dir/%2E%2E', $this->getGenerator($routes)->generate('test')); + $routes = $this->getRoutes('test', new Route('/dir/./dir/.')); + $this->assertSame('/app.php/dir/%2E/dir/%2E', $this->getGenerator($routes)->generate('test')); + $routes = $this->getRoutes('test', new Route('/a./.a/a../..a/...')); + $this->assertSame('/app.php/a./.a/a../..a/...', $this->getGenerator($routes)->generate('test')); + } + + public function testAdjacentVariables() + { + $routes = $this->getRoutes('test', new Route('/{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '\d+'))); + $generator = $this->getGenerator($routes); + $this->assertSame('/app.php/foo123', $generator->generate('test', array('x' => 'foo', 'y' => '123'))); + $this->assertSame('/app.php/foo123bar.xml', $generator->generate('test', array('x' => 'foo', 'y' => '123', 'z' => 'bar', '_format' => 'xml'))); + + // The default requirement for 'x' should not allow the separator '.' in this case because it would otherwise match everything + // and following optional variables like _format could never match. + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\InvalidParameterException'); + $generator->generate('test', array('x' => 'do.t', 'y' => '123', 'z' => 'bar', '_format' => 'xml')); + } + + public function testOptionalVariableWithNoRealSeparator() + { + $routes = $this->getRoutes('test', new Route('/get{what}', array('what' => 'All'))); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/get', $generator->generate('test')); + $this->assertSame('/app.php/getSites', $generator->generate('test', array('what' => 'Sites'))); + } + + public function testRequiredVariableWithNoRealSeparator() + { + $routes = $this->getRoutes('test', new Route('/get{what}Suffix')); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/getSitesSuffix', $generator->generate('test', array('what' => 'Sites'))); + } + + public function testDefaultRequirementOfVariable() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index', '_format' => 'mobile.html'))); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testDefaultRequirementOfVariableDisallowsSlash() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $this->getGenerator($routes)->generate('test', array('page' => 'index', '_format' => 'sl/ash')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testDefaultRequirementOfVariableDisallowsNextSeparator() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html')); + } + + public function testWithHostDifferentFromContext() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' => 'Fabien', 'locale' => 'fr'))); + } + + public function testWithHostSameAsContext() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr'))); + } + + public function testWithHostSameAsContextAndAbsolute() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL)); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterInHost() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterInHostWhenParamHasADefaultValue() + { + $routes = $this->getRoutes('test', new Route('/', array('foo' => 'bar'), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterEqualsDefaultValueInHost() + { + $routes = $this->getRoutes('test', new Route('/', array('foo' => 'baz'), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH); + } + + public function testUrlWithInvalidParameterInHostInNonStrictMode() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com')); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'baz'), UrlGeneratorInterface::ABSOLUTE_PATH)); + } + + public function testHostIsCaseInsensitive() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('locale' => 'en|de|fr'), array(), '{locale}.FooBar.com')); + $generator = $this->getGenerator($routes); + $this->assertSame('//EN.FooBar.com/app.php/', $generator->generate('test', array('locale' => 'EN'), UrlGeneratorInterface::NETWORK_PATH)); + } + + public function testGenerateNetworkPath() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com', array('http'))); + + $this->assertSame('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', + array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'network path with different host' + ); + $this->assertSame('//fr.example.com/app.php/Fabien?query=string', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', + array('name' => 'Fabien', 'locale' => 'fr', 'query' => 'string'), UrlGeneratorInterface::NETWORK_PATH), 'network path although host same as context' + ); + $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', + array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'absolute URL because scheme requirement does not match context' + ); + $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', + array('name' => 'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL), 'absolute URL with same scheme because it is requested' + ); + } + + public function testGenerateRelativePath() + { + $routes = new RouteCollection(); + $routes->add('article', new Route('/{author}/{article}/')); + $routes->add('comments', new Route('/{author}/{article}/comments')); + $routes->add('host', new Route('/{article}', array(), array(), array(), '{author}.example.com')); + $routes->add('scheme', new Route('/{author}/blog', array(), array(), array(), '', array('https'))); + $routes->add('unrelated', new Route('/about')); + + $generator = $this->getGenerator($routes, array('host' => 'example.com', 'pathInfo' => '/fabien/symfony-is-great/')); + + $this->assertSame('comments', $generator->generate('comments', + array('author' => 'fabien', 'article' => 'symfony-is-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('comments?page=2', $generator->generate('comments', + array('author' => 'fabien', 'article' => 'symfony-is-great', 'page' => 2), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../twig-is-great/', $generator->generate('article', + array('author' => 'fabien', 'article' => 'twig-is-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../../bernhard/forms-are-great/', $generator->generate('article', + array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('//bernhard.example.com/app.php/forms-are-great', $generator->generate('host', + array('author' => 'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('https://example.com/app.php/bernhard/blog', $generator->generate('scheme', + array('author' => 'bernhard'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../../about', $generator->generate('unrelated', + array(), UrlGeneratorInterface::RELATIVE_PATH) + ); + } + + /** + * @dataProvider provideRelativePaths + */ + public function testGetRelativePath($sourcePath, $targetPath, $expectedPath) + { + $this->assertSame($expectedPath, UrlGenerator::getRelativePath($sourcePath, $targetPath)); + } + + public function provideRelativePaths() + { + return array( + array( + '/same/dir/', + '/same/dir/', + '', + ), + array( + '/same/file', + '/same/file', + '', + ), + array( + '/', + '/file', + 'file', + ), + array( + '/', + '/dir/file', + 'dir/file', + ), + array( + '/dir/file.html', + '/dir/different-file.html', + 'different-file.html', + ), + array( + '/same/dir/extra-file', + '/same/dir/', + './', + ), + array( + '/parent/dir/', + '/parent/', + '../', + ), + array( + '/parent/dir/extra-file', + '/parent/', + '../', + ), + array( + '/a/b/', + '/x/y/z/', + '../../x/y/z/', + ), + array( + '/a/b/c/d/e', + '/a/c/d', + '../../../c/d', + ), + array( + '/a/b/c//', + '/a/b/c/', + '../', + ), + array( + '/a/b/c/', + '/a/b/c//', + './/', + ), + array( + '/root/a/b/c/', + '/root/x/b/c/', + '../../../x/b/c/', + ), + array( + '/a/b/c/d/', + '/a', + '../../../../a', + ), + array( + '/special-chars/sp%20ce/1€/mäh/e=mc²', + '/special-chars/sp%20ce/1€/<µ>/e=mc²', + '../<µ>/e=mc²', + ), + array( + 'not-rooted', + 'dir/file', + 'dir/file', + ), + array( + '//dir/', + '', + '../../', + ), + array( + '/dir/', + '/dir/file:with-colon', + './file:with-colon', + ), + array( + '/dir/', + '/dir/subdir/file:with-colon', + 'subdir/file:with-colon', + ), + array( + '/dir/', + '/dir/:subdir/', + './:subdir/', + ), + ); + } + + public function testFragmentsCanBeAppendedToUrls() + { + $routes = $this->getRoutes('test', new Route('/testing')); + + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => 'frag ment'), UrlGeneratorInterface::ABSOLUTE_PATH); + $this->assertEquals('/app.php/testing#frag%20ment', $url); + + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '0'), UrlGeneratorInterface::ABSOLUTE_PATH); + $this->assertEquals('/app.php/testing#0', $url); + } + + public function testFragmentsDoNotEscapeValidCharacters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('_fragment' => '?/'), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing#?/', $url); + } + + public function testFragmentsCanBeDefinedAsDefaults() + { + $routes = $this->getRoutes('test', new Route('/testing', array('_fragment' => 'fragment'))); + $url = $this->getGenerator($routes)->generate('test', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + + $this->assertEquals('/app.php/testing#fragment', $url); + } + + protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null) + { + $context = new RequestContext('/app.php'); + foreach ($parameters as $key => $value) { + $method = 'set'.$key; + $context->$method($value); + } + + return new UrlGenerator($routes, $context, $logger); + } + + protected function getRoutes($name, Route $route) + { + $routes = new RouteCollection(); + $routes->add($name, $route); + + return $routes; + } +} diff --git a/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php b/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.php new file mode 100644 index 00000000..e8bbe8fc --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.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\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; + +abstract class AbstractAnnotationLoaderTest extends TestCase +{ + public function getReader() + { + return $this->getMockBuilder('Doctrine\Common\Annotations\Reader') + ->disableOriginalConstructor() + ->getMock() + ; + } + + public function getClassLoader($reader) + { + return $this->getMockBuilder('Symfony\Component\Routing\Loader\AnnotationClassLoader') + ->setConstructorArgs(array($reader)) + ->getMockForAbstractClass() + ; + } +} diff --git a/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php b/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php new file mode 100644 index 00000000..bf2ab4ac --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Annotation\Route; + +class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + private $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = $this->getClassLoader($this->reader); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLoadMissingClass() + { + $this->loader->load('MissingClass'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLoadAbstractClass() + { + $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\AbstractClass'); + } + + /** + * @dataProvider provideTestSupportsChecksResource + */ + public function testSupportsChecksResource($resource, $expectedSupports) + { + $this->assertSame($expectedSupports, $this->loader->supports($resource), '->supports() returns true if the resource is loadable'); + } + + public function provideTestSupportsChecksResource() + { + return array( + array('class', true), + array('\fully\qualified\class\name', true), + array('namespaced\class\without\leading\slash', true), + array('ÿClassWithLegalSpecialCharacters', true), + array('5', false), + array('foo.foo', false), + array(null, false), + ); + } + + public function testSupportsChecksTypeIfSpecified() + { + $this->assertTrue($this->loader->supports('class', 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports('class', 'foo'), '->supports() checks the resource type if specified'); + } + + public function getLoadTests() + { + return array( + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('name' => 'route1', 'path' => '/path'), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('defaults' => array('arg2' => 'foo'), 'requirements' => array('arg3' => '\w+')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('options' => array('foo' => 'bar')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('schemes' => array('https'), 'methods' => array('GET')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('condition' => 'context.getMethod() == "GET"'), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3'), + ), + ); + } + + /** + * @dataProvider getLoadTests + */ + public function testLoad($className, $routeData = array(), $methodArgs = array()) + { + $routeData = array_replace(array( + 'name' => 'route', + 'path' => '/', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'schemes' => array(), + 'methods' => array(), + 'condition' => '', + ), $routeData); + + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($routeData)))) + ; + + $routeCollection = $this->loader->load($className); + $route = $routeCollection->get($routeData['name']); + + $this->assertSame($routeData['path'], $route->getPath(), '->load preserves path annotation'); + $this->assertCount( + count($routeData['requirements']), + array_intersect_assoc($routeData['requirements'], $route->getRequirements()), + '->load preserves requirements annotation' + ); + $this->assertCount( + count($routeData['options']), + array_intersect_assoc($routeData['options'], $route->getOptions()), + '->load preserves options annotation' + ); + $this->assertCount( + count($routeData['defaults']), + $route->getDefaults(), + '->load preserves defaults annotation' + ); + $this->assertEquals($routeData['schemes'], $route->getSchemes(), '->load preserves schemes annotation'); + $this->assertEquals($routeData['methods'], $route->getMethods(), '->load preserves methods annotation'); + $this->assertSame($routeData['condition'], $route->getCondition(), '->load preserves condition annotation'); + } + + public function testClassRouteLoad() + { + $classRouteData = array( + 'path' => '/prefix', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $methodRouteData = array( + 'name' => 'route1', + 'path' => '/path', + 'schemes' => array('http'), + 'methods' => array('POST', 'PUT'), + ); + + $this->reader + ->expects($this->once()) + ->method('getClassAnnotation') + ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData)))) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass'); + $route = $routeCollection->get($methodRouteData['name']); + + $this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path'); + $this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes'); + $this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods'); + } + + public function testInvokableClassRouteLoad() + { + $classRouteData = array( + 'name' => 'route1', + 'path' => '/', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $this->reader + ->expects($this->exactly(2)) + ->method('getClassAnnotation') + ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass'); + $route = $routeCollection->get($classRouteData['name']); + + $this->assertSame($classRouteData['path'], $route->getPath(), '->load preserves class route path'); + $this->assertEquals(array_merge($classRouteData['schemes'], $classRouteData['schemes']), $route->getSchemes(), '->load preserves class route schemes'); + $this->assertEquals(array_merge($classRouteData['methods'], $classRouteData['methods']), $route->getMethods(), '->load preserves class route methods'); + } + + public function testInvokableClassWithMethodRouteLoad() + { + $classRouteData = array( + 'name' => 'route1', + 'path' => '/prefix', + 'schemes' => array('https'), + 'methods' => array('GET'), + ); + + $methodRouteData = array( + 'name' => 'route2', + 'path' => '/path', + 'schemes' => array('http'), + 'methods' => array('POST', 'PUT'), + ); + + $this->reader + ->expects($this->once()) + ->method('getClassAnnotation') + ->will($this->returnValue($this->getAnnotatedRoute($classRouteData))) + ; + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($methodRouteData)))) + ; + + $routeCollection = $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass'); + $route = $routeCollection->get($classRouteData['name']); + + $this->assertNull($route, '->load ignores class route'); + + $route = $routeCollection->get($methodRouteData['name']); + + $this->assertSame($classRouteData['path'].$methodRouteData['path'], $route->getPath(), '->load concatenates class and method route path'); + $this->assertEquals(array_merge($classRouteData['schemes'], $methodRouteData['schemes']), $route->getSchemes(), '->load merges class and method route schemes'); + $this->assertEquals(array_merge($classRouteData['methods'], $methodRouteData['methods']), $route->getMethods(), '->load merges class and method route methods'); + } + + private function getAnnotatedRoute($data) + { + return new Route($data); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php b/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php new file mode 100644 index 00000000..78cec4be --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.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\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; +use Symfony\Component\Config\FileLocator; + +class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + protected $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = new AnnotationDirectoryLoader(new FileLocator(), $this->getClassLoader($this->reader)); + } + + public function testLoad() + { + $this->reader->expects($this->exactly(4))->method('getClassAnnotation'); + + $this->reader + ->expects($this->any()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses'); + } + + public function testLoadIgnoresHiddenDirectories() + { + $this->expectAnnotationsToBeReadFrom(array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass', + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BazClass', + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\FooClass', + )); + + $this->reader + ->expects($this->any()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses'); + } + + public function testSupports() + { + $fixturesDir = __DIR__.'/../Fixtures'; + + $this->assertTrue($this->loader->supports($fixturesDir), '->supports() returns true if the resource is loadable'); + $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($this->loader->supports($fixturesDir, 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports() checks the resource type if specified'); + } + + private function expectAnnotationsToBeReadFrom(array $classes) + { + $this->reader->expects($this->exactly(count($classes))) + ->method('getClassAnnotation') + ->with($this->callback(function (\ReflectionClass $class) use ($classes) { + return in_array($class->getName(), $classes); + })); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php new file mode 100644 index 00000000..5d54f9f9 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.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\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Annotation\Route; + +class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + protected $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = new AnnotationFileLoader(new FileLocator(), $this->getClassLoader($this->reader)); + } + + public function testLoad() + { + $this->reader->expects($this->once())->method('getClassAnnotation'); + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php'); + } + + /** + * @requires PHP 5.4 + */ + public function testLoadTraitWithClassConstant() + { + $this->reader->expects($this->never())->method('getClassAnnotation'); + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooTrait.php'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Did you forgot to add the "loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/NoStartTagClass.php'); + } + + /** + * @requires PHP 5.6 + */ + public function testLoadVariadic() + { + $route = new Route(array('path' => '/path/to/{id}')); + $this->reader->expects($this->once())->method('getClassAnnotation'); + $this->reader->expects($this->once())->method('getMethodAnnotations') + ->will($this->returnValue(array($route))); + + $this->loader->load(__DIR__.'/../Fixtures/OtherAnnotatedClasses/VariadicClass.php'); + } + + public function testSupports() + { + $fixture = __DIR__.'/../Fixtures/annotated.php'; + + $this->assertTrue($this->loader->supports($fixture), '->supports() returns true if the resource is loadable'); + $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($this->loader->supports($fixture, 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports($fixture, 'foo'), '->supports() checks the resource type if specified'); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php b/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php new file mode 100644 index 00000000..5d963f86 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Loader\ClosureLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ClosureLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new ClosureLoader(); + + $closure = function () {}; + + $this->assertTrue($loader->supports($closure), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports($closure, 'closure'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports($closure, 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoad() + { + $loader = new ClosureLoader(); + + $route = new Route('/'); + $routes = $loader->load(function () use ($route) { + $routes = new RouteCollection(); + + $routes->add('foo', $route); + + return $routes; + }); + + $this->assertEquals($route, $routes->get('foo'), '->load() loads a \Closure resource'); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php b/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php new file mode 100644 index 00000000..fc29d371 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/DirectoryLoaderTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\DirectoryLoader; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\RouteCollection; + +class DirectoryLoaderTest extends AbstractAnnotationLoaderTest +{ + private $loader; + private $reader; + + protected function setUp() + { + parent::setUp(); + + $locator = new FileLocator(); + $this->reader = $this->getReader(); + $this->loader = new DirectoryLoader($locator); + $resolver = new LoaderResolver(array( + new YamlFileLoader($locator), + new AnnotationFileLoader($locator, $this->getClassLoader($this->reader)), + $this->loader, + )); + $this->loader->setResolver($resolver); + } + + public function testLoadDirectory() + { + $collection = $this->loader->load(__DIR__.'/../Fixtures/directory', 'directory'); + $this->verifyCollection($collection); + } + + public function testImportDirectory() + { + $collection = $this->loader->load(__DIR__.'/../Fixtures/directory_import', 'directory'); + $this->verifyCollection($collection); + } + + private function verifyCollection(RouteCollection $collection) + { + $routes = $collection->all(); + + $this->assertCount(3, $routes, 'Three routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + for ($i = 1; $i <= 3; ++$i) { + $this->assertSame('/route/'.$i, $routes['route'.$i]->getPath()); + } + } + + public function testSupports() + { + $fixturesDir = __DIR__.'/../Fixtures'; + + $this->assertFalse($this->loader->supports($fixturesDir), '->supports(*) returns false'); + + $this->assertTrue($this->loader->supports($fixturesDir, 'directory'), '->supports(*, "directory") returns true'); + $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports(*, "foo") returns false'); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php b/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php new file mode 100644 index 00000000..408fa0b4 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Loader\ObjectRouteLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ObjectRouteLoaderTest extends TestCase +{ + public function testLoadCallsServiceAndReturnsCollection() + { + $loader = new ObjectRouteLoaderForTest(); + + // create a basic collection that will be returned + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $loader->loaderMap = array( + 'my_route_provider_service' => new RouteService($collection), + ); + + $actualRoutes = $loader->load( + 'my_route_provider_service:loadRoutes', + 'service' + ); + + $this->assertSame($collection, $actualRoutes); + // the service file should be listed as a resource + $this->assertNotEmpty($actualRoutes->getResources()); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getBadResourceStrings + */ + public function testExceptionWithoutSyntax($resourceString) + { + $loader = new ObjectRouteLoaderForTest(); + $loader->load($resourceString); + } + + public function getBadResourceStrings() + { + return array( + array('Foo'), + array('Bar::baz'), + array('Foo:Bar:baz'), + ); + } + + /** + * @expectedException \LogicException + */ + public function testExceptionOnNoObjectReturned() + { + $loader = new ObjectRouteLoaderForTest(); + $loader->loaderMap = array('my_service' => 'NOT_AN_OBJECT'); + $loader->load('my_service:method'); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testExceptionOnBadMethod() + { + $loader = new ObjectRouteLoaderForTest(); + $loader->loaderMap = array('my_service' => new \stdClass()); + $loader->load('my_service:method'); + } + + /** + * @expectedException \LogicException + */ + public function testExceptionOnMethodNotReturningCollection() + { + $service = $this->getMockBuilder('stdClass') + ->setMethods(array('loadRoutes')) + ->getMock(); + $service->expects($this->once()) + ->method('loadRoutes') + ->will($this->returnValue('NOT_A_COLLECTION')); + + $loader = new ObjectRouteLoaderForTest(); + $loader->loaderMap = array('my_service' => $service); + $loader->load('my_service:loadRoutes'); + } +} + +class ObjectRouteLoaderForTest extends ObjectRouteLoader +{ + public $loaderMap = array(); + + protected function getServiceObject($id) + { + return isset($this->loaderMap[$id]) ? $this->loaderMap[$id] : null; + } +} + +class RouteService +{ + private $collection; + + public function __construct($collection) + { + $this->collection = $collection; + } + + public function loadRoutes() + { + return $this->collection; + } +} diff --git a/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php new file mode 100644 index 00000000..bda64236 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\PhpFileLoader; + +class PhpFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new PhpFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock()); + + $this->assertTrue($loader->supports('foo.php'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.php', 'php'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.php', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadWithRoute() + { + $loader = new PhpFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.php'); + $routes = $routeCollection->all(); + + $this->assertCount(1, $routes, 'One route is loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + } + } + + public function testLoadWithImport() + { + $loader = new PhpFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.php'); + $routes = $routeCollection->all(); + + $this->assertCount(1, $routes, 'One route is loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/prefix/blog/{slug}', $route->getPath()); + $this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + } + } + + public function testThatDefiningVariableInConfigFileHasNoSideEffects() + { + $locator = new FileLocator(array(__DIR__.'/../Fixtures')); + $loader = new PhpFileLoader($locator); + $routeCollection = $loader->load('with_define_path_variable.php'); + $resources = $routeCollection->getResources(); + $this->assertCount(1, $resources); + $this->assertContainsOnly('Symfony\Component\Config\Resource\ResourceInterface', $resources); + $fileResource = reset($resources); + $this->assertSame( + realpath($locator->locate('with_define_path_variable.php')), + (string) $fileResource + ); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php new file mode 100644 index 00000000..d24ec79a --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.php @@ -0,0 +1,290 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Routing\Tests\Fixtures\CustomXmlFileLoader; + +class XmlFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new XmlFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock()); + + $this->assertTrue($loader->supports('foo.xml'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.xml', 'xml'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.xml', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadWithRoute() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.xml'); + $route = $routeCollection->get('blog_show'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('locale')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition()); + } + + public function testLoadWithNamespacePrefix() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('namespaceprefix.xml'); + + $this->assertCount(1, $routeCollection->all(), 'One route is loaded'); + + $route = $routeCollection->get('blog_show'); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{_locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('slug')); + $this->assertSame('en|fr|de', $route->getRequirement('_locale')); + $this->assertNull($route->getDefault('slug')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertSame(1, $route->getDefault('page')); + } + + public function testLoadWithImport() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.xml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/{foo}/blog/{slug}', $route->getPath()); + $this->assertSame('123', $route->getDefault('foo')); + $this->assertSame('\d+', $route->getRequirement('foo')); + $this->assertSame('bar', $route->getOption('foo')); + $this->assertSame('', $route->getHost()); + $this->assertSame('context.getMethod() == "POST"', $route->getCondition()); + } + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFile($filePath) + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFileEvenWithoutSchemaValidation($filePath) + { + $loader = new CustomXmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + public function getPathsToInvalidFiles() + { + return array(array('nonvalidnode.xml'), array('nonvalidroute.xml'), array('nonvalid.xml'), array('missing_id.xml'), array('missing_path.xml')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load('withdoctype.xml'); + } + + public function testNullValues() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('null_values.xml'); + $route = $routeCollection->get('blog_show'); + + $this->assertTrue($route->hasDefault('foo')); + $this->assertNull($route->getDefault('foo')); + $this->assertTrue($route->hasDefault('bar')); + $this->assertNull($route->getDefault('bar')); + $this->assertEquals('foo', $route->getDefault('foobar')); + $this->assertEquals('bar', $route->getDefault('baz')); + } + + public function testScalarDataTypeDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('scalar_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'slug' => null, + 'published' => true, + 'page' => 1, + 'price' => 3.5, + 'archived' => false, + 'free' => true, + 'locked' => false, + 'foo' => null, + 'bar' => null, + ), + $route->getDefaults() + ); + } + + public function testListDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array(true, 1, 3.5, 'foo'), + ), + $route->getDefaults() + ); + } + + public function testListInListDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_in_list_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array(array(true, 1, 3.5, 'foo')), + ), + $route->getDefaults() + ); + } + + public function testListInMapDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_in_map_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array('list' => array(true, 1, 3.5, 'foo')), + ), + $route->getDefaults() + ); + } + + public function testMapDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array( + 'public' => true, + 'page' => 1, + 'price' => 3.5, + 'title' => 'foo', + ), + ), + $route->getDefaults() + ); + } + + public function testMapInListDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_in_list_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array(array( + 'public' => true, + 'page' => 1, + 'price' => 3.5, + 'title' => 'foo', + )), + ), + $route->getDefaults() + ); + } + + public function testMapInMapDefaults() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_in_map_defaults.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + '_controller' => 'AcmeBlogBundle:Blog:index', + 'values' => array('map' => array( + 'public' => true, + 'page' => 1, + 'price' => 3.5, + 'title' => 'foo', + )), + ), + $route->getDefaults() + ); + } + + public function testNullValuesInList() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('list_null_values.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame(array(null, null, null, null, null, null), $route->getDefault('list')); + } + + public function testNullValuesInMap() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('map_null_values.xml'); + $route = $routeCollection->get('blog'); + + $this->assertSame( + array( + 'boolean' => null, + 'integer' => null, + 'float' => null, + 'string' => null, + 'list' => null, + 'map' => null, + ), + $route->getDefault('map') + ); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php new file mode 100644 index 00000000..8342c266 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class YamlFileLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new YamlFileLoader($this->getMockBuilder('Symfony\Component\Config\FileLocator')->getMock()); + + $this->assertTrue($loader->supports('foo.yml'), '->supports() returns true if the resource is loadable'); + $this->assertTrue($loader->supports('foo.yaml'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.yml', 'yaml'), '->supports() checks the resource type if specified'); + $this->assertTrue($loader->supports('foo.yaml', 'yaml'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.yml', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadDoesNothingIfEmpty() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $collection = $loader->load('empty.yml'); + + $this->assertEquals(array(), $collection->all()); + $this->assertEquals(array(new FileResource(realpath(__DIR__.'/../Fixtures/empty.yml'))), $collection->getResources()); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFile($filePath) + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + public function getPathsToInvalidFiles() + { + return array( + array('nonvalid.yml'), + array('nonvalid2.yml'), + array('incomplete.yml'), + array('nonvalidkeys.yml'), + array('nonesense_resource_plus_path.yml'), + array('nonesense_type_without_resource.yml'), + array('bad_format.yml'), + ); + } + + public function testLoadSpecialRouteName() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('special_route_name.yml'); + $route = $routeCollection->get('#$péß^a|'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/true', $route->getPath()); + } + + public function testLoadWithRoute() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.yml'); + $route = $routeCollection->get('blog_show'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('locale')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition()); + } + + public function testLoadWithResource() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.yml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/{foo}/blog/{slug}', $route->getPath()); + $this->assertSame('123', $route->getDefault('foo')); + $this->assertSame('\d+', $route->getRequirement('foo')); + $this->assertSame('bar', $route->getOption('foo')); + $this->assertSame('', $route->getHost()); + $this->assertSame('context.getMethod() == "POST"', $route->getCondition()); + } + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php b/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php new file mode 100644 index 00000000..823efdb8 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; + +class DumperCollectionTest extends TestCase +{ + public function testGetRoot() + { + $a = new DumperCollection(); + + $b = new DumperCollection(); + $a->add($b); + + $c = new DumperCollection(); + $b->add($c); + + $d = new DumperCollection(); + $c->add($d); + + $this->assertSame($a, $c->getRoot()); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php new file mode 100644 index 00000000..9d4f086b --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -0,0 +1,377 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class PhpMatcherDumperTest extends TestCase +{ + /** + * @expectedException \LogicException + */ + public function testDumpWhenSchemeIsUsedWithoutAProperDumper() + { + $collection = new RouteCollection(); + $collection->add('secure', new Route( + '/secure', + array(), + array(), + array(), + '', + array('https') + )); + $dumper = new PhpMatcherDumper($collection); + $dumper->dump(); + } + + /** + * @dataProvider getRouteCollections + */ + public function testDump(RouteCollection $collection, $fixture, $options = array()) + { + $basePath = __DIR__.'/../../Fixtures/dumper/'; + + $dumper = new PhpMatcherDumper($collection); + $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.'); + } + + public function getRouteCollections() + { + /* test case 1 */ + + $collection = new RouteCollection(); + + $collection->add('overridden', new Route('/overridden')); + + // defaults and requirements + $collection->add('foo', new Route( + '/foo/{bar}', + array('def' => 'test'), + array('bar' => 'baz|symfony') + )); + // method requirement + $collection->add('bar', new Route( + '/bar/{foo}', + array(), + array(), + array(), + '', + array(), + array('GET', 'head') + )); + // GET method requirement automatically adds HEAD as valid + $collection->add('barhead', new Route( + '/barhead/{foo}', + array(), + array(), + array(), + '', + array(), + array('GET') + )); + // simple + $collection->add('baz', new Route( + '/test/baz' + )); + // simple with extension + $collection->add('baz2', new Route( + '/test/baz.html' + )); + // trailing slash + $collection->add('baz3', new Route( + '/test/baz3/' + )); + // trailing slash with variable + $collection->add('baz4', new Route( + '/test/{foo}/' + )); + // trailing slash and method + $collection->add('baz5', new Route( + '/test/{foo}/', + array(), + array(), + array(), + '', + array(), + array('post') + )); + // complex name + $collection->add('baz.baz6', new Route( + '/test/{foo}/', + array(), + array(), + array(), + '', + array(), + array('put') + )); + // defaults without variable + $collection->add('foofoo', new Route( + '/foofoo', + array('def' => 'test') + )); + // pattern with quotes + $collection->add('quoter', new Route( + '/{quoter}', + array(), + array('quoter' => '[\']+') + )); + // space in pattern + $collection->add('space', new Route( + '/spa ce' + )); + + // prefixes + $collection1 = new RouteCollection(); + $collection1->add('overridden', new Route('/overridden1')); + $collection1->add('foo1', new Route('/{foo}')); + $collection1->add('bar1', new Route('/{bar}')); + $collection1->addPrefix('/b\'b'); + $collection2 = new RouteCollection(); + $collection2->addCollection($collection1); + $collection2->add('overridden', new Route('/{var}', array(), array('var' => '.*'))); + $collection1 = new RouteCollection(); + $collection1->add('foo2', new Route('/{foo1}')); + $collection1->add('bar2', new Route('/{bar1}')); + $collection1->addPrefix('/b\'b'); + $collection2->addCollection($collection1); + $collection2->addPrefix('/a'); + $collection->addCollection($collection2); + + // overridden through addCollection() and multiple sub-collections with no own prefix + $collection1 = new RouteCollection(); + $collection1->add('overridden2', new Route('/old')); + $collection1->add('helloWorld', new Route('/hello/{who}', array('who' => 'World!'))); + $collection2 = new RouteCollection(); + $collection3 = new RouteCollection(); + $collection3->add('overridden2', new Route('/new')); + $collection3->add('hey', new Route('/hey/')); + $collection2->addCollection($collection3); + $collection1->addCollection($collection2); + $collection1->addPrefix('/multi'); + $collection->addCollection($collection1); + + // "dynamic" prefix + $collection1 = new RouteCollection(); + $collection1->add('foo3', new Route('/{foo}')); + $collection1->add('bar3', new Route('/{bar}')); + $collection1->addPrefix('/b'); + $collection1->addPrefix('{_locale}'); + $collection->addCollection($collection1); + + // route between collections + $collection->add('ababa', new Route('/ababa')); + + // collection with static prefix but only one route + $collection1 = new RouteCollection(); + $collection1->add('foo4', new Route('/{foo}')); + $collection1->addPrefix('/aba'); + $collection->addCollection($collection1); + + // prefix and host + + $collection1 = new RouteCollection(); + + $route1 = new Route('/route1', array(), array(), array(), 'a.example.com'); + $collection1->add('route1', $route1); + + $route2 = new Route('/c2/route2', array(), array(), array(), 'a.example.com'); + $collection1->add('route2', $route2); + + $route3 = new Route('/c2/route3', array(), array(), array(), 'b.example.com'); + $collection1->add('route3', $route3); + + $route4 = new Route('/route4', array(), array(), array(), 'a.example.com'); + $collection1->add('route4', $route4); + + $route5 = new Route('/route5', array(), array(), array(), 'c.example.com'); + $collection1->add('route5', $route5); + + $route6 = new Route('/route6', array(), array(), array(), null); + $collection1->add('route6', $route6); + + $collection->addCollection($collection1); + + // host and variables + + $collection1 = new RouteCollection(); + + $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route11', $route11); + + $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route12', $route12); + + $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route13', $route13); + + $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route14', $route14); + + $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com'); + $collection1->add('route15', $route15); + + $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null); + $collection1->add('route16', $route16); + + $route17 = new Route('/route17', array(), array(), array(), null); + $collection1->add('route17', $route17); + + $collection->addCollection($collection1); + + // multiple sub-collections with a single route and a prefix each + $collection1 = new RouteCollection(); + $collection1->add('a', new Route('/a...')); + $collection2 = new RouteCollection(); + $collection2->add('b', new Route('/{var}')); + $collection3 = new RouteCollection(); + $collection3->add('c', new Route('/{var}')); + $collection3->addPrefix('/c'); + $collection2->addCollection($collection3); + $collection2->addPrefix('/b'); + $collection1->addCollection($collection2); + $collection1->addPrefix('/a'); + $collection->addCollection($collection1); + + /* test case 2 */ + + $redirectCollection = clone $collection; + + // force HTTPS redirection + $redirectCollection->add('secure', new Route( + '/secure', + array(), + array(), + array(), + '', + array('https') + )); + + // force HTTP redirection + $redirectCollection->add('nonsecure', new Route( + '/nonsecure', + array(), + array(), + array(), + '', + array('http') + )); + + /* test case 3 */ + + $rootprefixCollection = new RouteCollection(); + $rootprefixCollection->add('static', new Route('/test')); + $rootprefixCollection->add('dynamic', new Route('/{var}')); + $rootprefixCollection->addPrefix('rootprefix'); + $route = new Route('/with-condition'); + $route->setCondition('context.getMethod() == "GET"'); + $rootprefixCollection->add('with-condition', $route); + + /* test case 4 */ + $headMatchCasesCollection = new RouteCollection(); + $headMatchCasesCollection->add('just_head', new Route( + '/just_head', + array(), + array(), + array(), + '', + array(), + array('HEAD') + )); + $headMatchCasesCollection->add('head_and_get', new Route( + '/head_and_get', + array(), + array(), + array(), + '', + array(), + array('GET', 'HEAD') + )); + $headMatchCasesCollection->add('post_and_head', new Route( + '/post_and_get', + array(), + array(), + array(), + '', + array(), + array('POST', 'HEAD') + )); + $headMatchCasesCollection->add('put_and_post', new Route( + '/put_and_post', + array(), + array(), + array(), + '', + array(), + array('PUT', 'POST') + )); + $headMatchCasesCollection->add('put_and_get_and_head', new Route( + '/put_and_post', + array(), + array(), + array(), + '', + array(), + array('PUT', 'GET', 'HEAD') + )); + + /* test case 5 */ + $groupOptimisedCollection = new RouteCollection(); + $groupOptimisedCollection->add('a_first', new Route('/a/11')); + $groupOptimisedCollection->add('a_second', new Route('/a/22')); + $groupOptimisedCollection->add('a_third', new Route('/a/333')); + $groupOptimisedCollection->add('a_wildcard', new Route('/{param}')); + $groupOptimisedCollection->add('a_fourth', new Route('/a/44/')); + $groupOptimisedCollection->add('a_fifth', new Route('/a/55/')); + $groupOptimisedCollection->add('a_sixth', new Route('/a/66/')); + $groupOptimisedCollection->add('nested_wildcard', new Route('/nested/{param}')); + $groupOptimisedCollection->add('nested_a', new Route('/nested/group/a/')); + $groupOptimisedCollection->add('nested_b', new Route('/nested/group/b/')); + $groupOptimisedCollection->add('nested_c', new Route('/nested/group/c/')); + + $groupOptimisedCollection->add('slashed_a', new Route('/slashed/group/')); + $groupOptimisedCollection->add('slashed_b', new Route('/slashed/group/b/')); + $groupOptimisedCollection->add('slashed_c', new Route('/slashed/group/c/')); + + $trailingSlashCollection = new RouteCollection(); + $trailingSlashCollection->add('simple_trailing_slash_no_methods', new Route('/trailing/simple/no-methods/', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('simple_trailing_slash_GET_method', new Route('/trailing/simple/get-method/', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('simple_trailing_slash_HEAD_method', new Route('/trailing/simple/head-method/', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('simple_trailing_slash_POST_method', new Route('/trailing/simple/post-method/', array(), array(), array(), '', array(), array('POST'))); + $trailingSlashCollection->add('regex_trailing_slash_no_methods', new Route('/trailing/regex/no-methods/{param}/', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('regex_trailing_slash_GET_method', new Route('/trailing/regex/get-method/{param}/', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('regex_trailing_slash_HEAD_method', new Route('/trailing/regex/head-method/{param}/', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('regex_trailing_slash_POST_method', new Route('/trailing/regex/post-method/{param}/', array(), array(), array(), '', array(), array('POST'))); + + $trailingSlashCollection->add('simple_not_trailing_slash_no_methods', new Route('/not-trailing/simple/no-methods', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('simple_not_trailing_slash_GET_method', new Route('/not-trailing/simple/get-method', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('simple_not_trailing_slash_HEAD_method', new Route('/not-trailing/simple/head-method', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('simple_not_trailing_slash_POST_method', new Route('/not-trailing/simple/post-method', array(), array(), array(), '', array(), array('POST'))); + $trailingSlashCollection->add('regex_not_trailing_slash_no_methods', new Route('/not-trailing/regex/no-methods/{param}', array(), array(), array(), '', array(), array())); + $trailingSlashCollection->add('regex_not_trailing_slash_GET_method', new Route('/not-trailing/regex/get-method/{param}', array(), array(), array(), '', array(), array('GET'))); + $trailingSlashCollection->add('regex_not_trailing_slash_HEAD_method', new Route('/not-trailing/regex/head-method/{param}', array(), array(), array(), '', array(), array('HEAD'))); + $trailingSlashCollection->add('regex_not_trailing_slash_POST_method', new Route('/not-trailing/regex/post-method/{param}', array(), array(), array(), '', array(), array('POST'))); + + return array( + array($collection, 'url_matcher1.php', array()), + array($redirectCollection, 'url_matcher2.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + array($rootprefixCollection, 'url_matcher3.php', array()), + array($headMatchCasesCollection, 'url_matcher4.php', array()), + array($groupOptimisedCollection, 'url_matcher5.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + array($trailingSlashCollection, 'url_matcher6.php', array()), + array($trailingSlashCollection, 'url_matcher7.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + ); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php b/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php new file mode 100644 index 00000000..37419e77 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php @@ -0,0 +1,175 @@ +compile()->getStaticPrefix(); + $collection->addRoute($staticPrefix, $name); + } + + $collection->optimizeGroups(); + $dumped = $this->dumpCollection($collection); + $this->assertEquals($expected, $dumped); + } + + public function routeProvider() + { + return array( + 'Simple - not nested' => array( + array( + array('/', 'root'), + array('/prefix/segment/', 'prefix_segment'), + array('/leading/segment/', 'leading_segment'), + ), + << array( + array( + array('/', 'root'), + array('/prefix/segment/aa', 'prefix_segment'), + array('/prefix/segment/bb', 'leading_segment'), + ), + << array( + array( + array('/', 'root'), + array('/prefix/segment/', 'prefix_segment'), + array('/prefix/segment/bb', 'leading_segment'), + ), + << /prefix/segment prefix_segment +-> /prefix/segment/bb leading_segment +EOF + ), + 'Simple one level nesting' => array( + array( + array('/', 'root'), + array('/group/segment/', 'nested_segment'), + array('/group/thing/', 'some_segment'), + array('/group/other/', 'other_segment'), + ), + << /group/segment nested_segment +-> /group/thing some_segment +-> /group/other other_segment +EOF + ), + 'Retain matching order with groups' => array( + array( + array('/group/aa/', 'aa'), + array('/group/bb/', 'bb'), + array('/group/cc/', 'cc'), + array('/', 'root'), + array('/group/dd/', 'dd'), + array('/group/ee/', 'ee'), + array('/group/ff/', 'ff'), + ), + << /group/aa aa +-> /group/bb bb +-> /group/cc cc +/ root +/group +-> /group/dd dd +-> /group/ee ee +-> /group/ff ff +EOF + ), + 'Retain complex matching order with groups at base' => array( + array( + array('/aaa/111/', 'first_aaa'), + array('/prefixed/group/aa/', 'aa'), + array('/prefixed/group/bb/', 'bb'), + array('/prefixed/group/cc/', 'cc'), + array('/prefixed/', 'root'), + array('/prefixed/group/dd/', 'dd'), + array('/prefixed/group/ee/', 'ee'), + array('/prefixed/group/ff/', 'ff'), + array('/aaa/222/', 'second_aaa'), + array('/aaa/333/', 'third_aaa'), + ), + << /aaa/111 first_aaa +-> /aaa/222 second_aaa +-> /aaa/333 third_aaa +/prefixed +-> /prefixed/group +-> -> /prefixed/group/aa aa +-> -> /prefixed/group/bb bb +-> -> /prefixed/group/cc cc +-> /prefixed root +-> /prefixed/group +-> -> /prefixed/group/dd dd +-> -> /prefixed/group/ee ee +-> -> /prefixed/group/ff ff +EOF + ), + + 'Group regardless of segments' => array( + array( + array('/aaa-111/', 'a1'), + array('/aaa-222/', 'a2'), + array('/aaa-333/', 'a3'), + array('/group-aa/', 'g1'), + array('/group-bb/', 'g2'), + array('/group-cc/', 'g3'), + ), + << /aaa-111 a1 +-> /aaa-222 a2 +-> /aaa-333 a3 +/group- +-> /group-aa g1 +-> /group-bb g2 +-> /group-cc g3 +EOF + ), + ); + } + + private function dumpCollection(StaticPrefixCollection $collection, $prefix = '') + { + $lines = array(); + + foreach ($collection->getItems() as $item) { + if ($item instanceof StaticPrefixCollection) { + $lines[] = $prefix.$item->getPrefix(); + $lines[] = $this->dumpCollection($item, $prefix.'-> '); + } else { + $lines[] = $prefix.implode(' ', $item); + } + } + + return implode("\n", $lines); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php new file mode 100644 index 00000000..ba4c6e97 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class RedirectableUrlMatcherTest extends TestCase +{ + public function testRedirectWhenNoSlash() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/')); + + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher->expects($this->once())->method('redirect'); + $matcher->match('/foo'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testRedirectWhenNoSlashForNonSafeMethod() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/')); + + $context = new RequestContext(); + $context->setMethod('POST'); + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, $context)); + $matcher->match('/foo'); + } + + public function testSchemeRedirectRedirectsToFirstScheme() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('FTP', 'HTTPS'))); + + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher + ->expects($this->once()) + ->method('redirect') + ->with('/foo', 'foo', 'ftp') + ->will($this->returnValue(array('_route' => 'foo'))) + ; + $matcher->match('/foo'); + } + + public function testNoSchemaRedirectIfOnOfMultipleSchemesMatches() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https', 'http'))); + + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher + ->expects($this->never()) + ->method('redirect') + ; + $matcher->match('/foo'); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php b/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.php new file mode 100644 index 00000000..9f0529e2 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.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\Routing\Tests\Matcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; + +class TraceableUrlMatcherTest extends TestCase +{ + public function test() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('POST'))); + $coll->add('bar', new Route('/bar/{id}', array(), array('id' => '\d+'))); + $coll->add('bar1', new Route('/bar/{name}', array(), array('id' => '\w+'), array(), '', array(), array('POST'))); + $coll->add('bar2', new Route('/foo', array(), array(), array(), 'baz')); + $coll->add('bar3', new Route('/foo1', array(), array(), array(), 'baz')); + $coll->add('bar4', new Route('/foo2', array(), array(), array(), 'baz', array(), array(), 'context.getMethod() == "GET"')); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($coll, $context); + $traces = $matcher->getTraces('/babar'); + $this->assertSame(array(0, 0, 0, 0, 0, 0), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo'); + $this->assertSame(array(1, 0, 0, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/12'); + $this->assertSame(array(0, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/dd'); + $this->assertSame(array(0, 1, 1, 0, 0, 0), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo1'); + $this->assertSame(array(0, 0, 0, 0, 2), $this->getLevels($traces)); + + $context->setMethod('POST'); + $traces = $matcher->getTraces('/foo'); + $this->assertSame(array(2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/dd'); + $this->assertSame(array(0, 1, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo2'); + $this->assertSame(array(0, 0, 0, 0, 0, 1), $this->getLevels($traces)); + } + + public function testMatchRouteOnMultipleHosts() + { + $routes = new RouteCollection(); + $routes->add('first', new Route( + '/mypath/', + array('_controller' => 'MainBundle:Info:first'), + array(), + array(), + 'some.example.com' + )); + + $routes->add('second', new Route( + '/mypath/', + array('_controller' => 'MainBundle:Info:second'), + array(), + array(), + 'another.example.com' + )); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($routes, $context); + + $traces = $matcher->getTraces('/mypath/'); + $this->assertSame( + array(TraceableUrlMatcher::ROUTE_ALMOST_MATCHES, TraceableUrlMatcher::ROUTE_ALMOST_MATCHES), + $this->getLevels($traces) + ); + } + + public function getLevels($traces) + { + $levels = array(); + foreach ($traces as $trace) { + $levels[] = $trace['level']; + } + + return $levels; + } + + public function testRoutesWithConditions() + { + $routes = new RouteCollection(); + $routes->add('foo', new Route('/foo', array(), array(), array(), 'baz', array(), array(), "request.headers.get('User-Agent') matches '/firefox/i'")); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($routes, $context); + + $notMatchingRequest = Request::create('/foo', 'GET'); + $traces = $matcher->getTracesForRequest($notMatchingRequest); + $this->assertEquals("Condition \"request.headers.get('User-Agent') matches '/firefox/i'\" does not evaluate to \"true\"", $traces[0]['log']); + + $matchingRequest = Request::create('/foo', 'GET', array(), array(), array(), array('HTTP_USER_AGENT' => 'Firefox')); + $traces = $matcher->getTracesForRequest($matchingRequest); + $this->assertEquals('Route matches!', $traces[0]['log']); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php b/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php new file mode 100644 index 00000000..06a7779a --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php @@ -0,0 +1,420 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class UrlMatcherTest extends TestCase +{ + public function testNoMethodSoAllowed() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo')); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertInternalType('array', $matcher->match('/foo')); + } + + public function testMethodNotAllowed() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('post'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + $this->assertEquals(array('POST'), $e->getAllowedMethods()); + } + } + + public function testHeadAllowedWhenRequirementContainsGet() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get'))); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'head')); + $this->assertInternalType('array', $matcher->match('/foo')); + } + + public function testMethodNotAllowedAggregatesAllowedMethods() + { + $coll = new RouteCollection(); + $coll->add('foo1', new Route('/foo', array(), array(), array(), '', array(), array('post'))); + $coll->add('foo2', new Route('/foo', array(), array(), array(), '', array(), array('put', 'delete'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + $this->assertEquals(array('POST', 'PUT', 'DELETE'), $e->getAllowedMethods()); + } + } + + public function testMatch() + { + // test the patterns are matched and parameters are returned + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo/{bar}')); + $matcher = new UrlMatcher($collection, new RequestContext()); + try { + $matcher->match('/no-match'); + $this->fail(); + } catch (ResourceNotFoundException $e) { + } + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz'), $matcher->match('/foo/baz')); + + // test that defaults are merged + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo/{bar}', array('def' => 'test'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'def' => 'test'), $matcher->match('/foo/baz')); + + // test that route "method" is ignored if no method is given in the context + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo', array(), array(), array(), '', array(), array('get', 'head'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertInternalType('array', $matcher->match('/foo')); + + // route does not match with POST method context + $matcher = new UrlMatcher($collection, new RequestContext('', 'post')); + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + } + + // route does match with GET or HEAD method context + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertInternalType('array', $matcher->match('/foo')); + $matcher = new UrlMatcher($collection, new RequestContext('', 'head')); + $this->assertInternalType('array', $matcher->match('/foo')); + + // route with an optional variable as the first segment + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{bar}/foo', array('bar' => 'bar'), array('bar' => 'foo|bar'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/bar/foo')); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo/foo')); + + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{bar}', array('bar' => 'bar'), array('bar' => 'foo|bar'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo')); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/')); + + // route with only optional variables + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar'), array())); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'bar' => 'bar'), $matcher->match('/')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'bar'), $matcher->match('/a')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'b'), $matcher->match('/a/b')); + } + + public function testMatchWithPrefixes() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}')); + $collection->addPrefix('/b'); + $collection->addPrefix('/a'); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'foo' => 'foo'), $matcher->match('/a/b/foo')); + } + + public function testMatchWithDynamicPrefix() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}')); + $collection->addPrefix('/b'); + $collection->addPrefix('/{_locale}'); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_locale' => 'fr', '_route' => 'foo', 'foo' => 'foo'), $matcher->match('/fr/b/foo')); + } + + public function testMatchSpecialRouteName() + { + $collection = new RouteCollection(); + $collection->add('$péß^a|', new Route('/bar')); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => '$péß^a|'), $matcher->match('/bar')); + } + + public function testMatchNonAlpha() + { + $collection = new RouteCollection(); + $chars = '!"$%éà &\'()*+,./:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[]^_`abcdefghijklmnopqrstuvwxyz{|}~-'; + $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '['.preg_quote($chars).']+'), array('utf8' => true))); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.rawurlencode($chars).'/bar')); + $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.strtr($chars, array('%' => '%25')).'/bar')); + } + + public function testMatchWithDotMetacharacterInRequirements() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '.+'))); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'foo' => "\n"), $matcher->match('/'.urlencode("\n").'/bar'), 'linefeed character is matched'); + } + + public function testMatchOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/foo1')); + + $collection->addCollection($collection1); + + $matcher = new UrlMatcher($collection, new RequestContext()); + + $this->assertEquals(array('_route' => 'foo'), $matcher->match('/foo1')); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $this->assertEquals(array(), $matcher->match('/foo')); + } + + public function testMatchRegression() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + $coll->add('bar', new Route('/foo/bar/{foo}')); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('foo' => 'bar', '_route' => 'bar'), $matcher->match('/foo/bar/bar')); + + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{bar}')); + $matcher = new UrlMatcher($collection, new RequestContext()); + try { + $matcher->match('/'); + $this->fail(); + } catch (ResourceNotFoundException $e) { + } + } + + public function testDefaultRequirementForOptionalVariables() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}', array('page' => 'index', '_format' => 'html'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('page' => 'my-page', '_format' => 'xml', '_route' => 'test'), $matcher->match('/my-page.xml')); + } + + public function testMatchingIsEager() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{foo}-{bar}-', array(), array('foo' => '.+', 'bar' => '.+'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('foo' => 'text1-text2-text3', 'bar' => 'text4', '_route' => 'test'), $matcher->match('/text1-text2-text3-text4-')); + } + + public function testAdjacentVariables() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => 'y|Y'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + // 'w' eagerly matches as much as possible and the other variables match the remaining chars. + // This also shows that the variables w-z must all exclude the separating char (the dot '.' in this case) by default requirement. + // Otherwise they would also consume '.xml' and _format would never match as it's an optional variable. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'Y', 'z' => 'Z', '_format' => 'xml', '_route' => 'test'), $matcher->match('/wwwwwxYZ.xml')); + // As 'y' has custom requirement and can only be of value 'y|Y', it will leave 'ZZZ' to variable z. + // So with carefully chosen requirements adjacent variables, can be useful. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'ZZZ', '_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxyZZZ')); + // z and _format are optional. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'default-z', '_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxy')); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $matcher->match('/wxy.html'); + } + + public function testOptionalVariableWithNoRealSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/get{what}', array('what' => 'All'))); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('what' => 'All', '_route' => 'test'), $matcher->match('/get')); + $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSites')); + + // Usually the character in front of an optional parameter can be left out, e.g. with pattern '/get/{what}' just '/get' would match. + // But here the 't' in 'get' is not a separating character, so it makes no sense to match without it. + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $matcher->match('/ge'); + } + + public function testRequiredVariableWithNoRealSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/get{what}Suffix')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSitesSuffix')); + } + + public function testDefaultRequirementOfVariable() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('page' => 'index', '_format' => 'mobile.html', '_route' => 'test'), $matcher->match('/index.mobile.html')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testDefaultRequirementOfVariableDisallowsSlash() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $matcher->match('/index.sl/ash'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testDefaultRequirementOfVariableDisallowsNextSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}', array(), array('_format' => 'html|xml'))); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $matcher->match('/do.t.html'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testSchemeRequirement() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array(), array(), '', array('https'))); + $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher->match('/foo'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testCondition() + { + $coll = new RouteCollection(); + $route = new Route('/foo'); + $route->setCondition('context.getMethod() == "POST"'); + $coll->add('foo', $route); + $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher->match('/foo'); + } + + public function testDecodeOnce() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('foo' => 'bar%23', '_route' => 'foo'), $matcher->match('/foo/bar%2523')); + } + + public function testCannotRelyOnPrefix() + { + $coll = new RouteCollection(); + + $subColl = new RouteCollection(); + $subColl->add('bar', new Route('/bar')); + $subColl->addPrefix('/prefix'); + // overwrite the pattern, so the prefix is not valid anymore for this route in the collection + $subColl->get('bar')->setPath('/new'); + + $coll->addCollection($subColl); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('_route' => 'bar'), $matcher->match('/new')); + } + + public function testWithHost() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); + } + + public function testWithHostOnRouteCollection() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + $coll->add('bar', new Route('/bar/{foo}', array(), array(), array(), '{locale}.example.net')); + $coll->setHost('{locale}.example.com'); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'bar', 'locale' => 'en'), $matcher->match('/bar/bar')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testWithOutHostHostDoesNotMatch() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'example.com')); + $matcher->match('/foo/bar'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testPathIsCaseSensitive() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/locale', array(), array('locale' => 'EN|FR|DE'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher->match('/en'); + } + + public function testHostIsCaseInsensitive() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/', array(), array('locale' => 'EN|FR|DE'), array(), '{locale}.example.com')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('_route' => 'foo', 'locale' => 'en'), $matcher->match('/')); + } +} diff --git a/vendor/symfony/routing/Tests/RequestContextTest.php b/vendor/symfony/routing/Tests/RequestContextTest.php new file mode 100644 index 00000000..ffe29d1a --- /dev/null +++ b/vendor/symfony/routing/Tests/RequestContextTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\RequestContext; + +class RequestContextTest extends TestCase +{ + public function testConstruct() + { + $requestContext = new RequestContext( + 'foo', + 'post', + 'foo.bar', + 'HTTPS', + 8080, + 444, + '/baz', + 'bar=foobar' + ); + + $this->assertEquals('foo', $requestContext->getBaseUrl()); + $this->assertEquals('POST', $requestContext->getMethod()); + $this->assertEquals('foo.bar', $requestContext->getHost()); + $this->assertEquals('https', $requestContext->getScheme()); + $this->assertSame(8080, $requestContext->getHttpPort()); + $this->assertSame(444, $requestContext->getHttpsPort()); + $this->assertEquals('/baz', $requestContext->getPathInfo()); + $this->assertEquals('bar=foobar', $requestContext->getQueryString()); + } + + public function testFromRequest() + { + $request = Request::create('https://test.com:444/foo?bar=baz'); + $requestContext = new RequestContext(); + $requestContext->setHttpPort(123); + $requestContext->fromRequest($request); + + $this->assertEquals('', $requestContext->getBaseUrl()); + $this->assertEquals('GET', $requestContext->getMethod()); + $this->assertEquals('test.com', $requestContext->getHost()); + $this->assertEquals('https', $requestContext->getScheme()); + $this->assertEquals('/foo', $requestContext->getPathInfo()); + $this->assertEquals('bar=baz', $requestContext->getQueryString()); + $this->assertSame(123, $requestContext->getHttpPort()); + $this->assertSame(444, $requestContext->getHttpsPort()); + + $request = Request::create('http://test.com:8080/foo?bar=baz'); + $requestContext = new RequestContext(); + $requestContext->setHttpsPort(567); + $requestContext->fromRequest($request); + + $this->assertSame(8080, $requestContext->getHttpPort()); + $this->assertSame(567, $requestContext->getHttpsPort()); + } + + public function testGetParameters() + { + $requestContext = new RequestContext(); + $this->assertEquals(array(), $requestContext->getParameters()); + + $requestContext->setParameters(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $requestContext->getParameters()); + } + + public function testHasParameter() + { + $requestContext = new RequestContext(); + $requestContext->setParameters(array('foo' => 'bar')); + + $this->assertTrue($requestContext->hasParameter('foo')); + $this->assertFalse($requestContext->hasParameter('baz')); + } + + public function testGetParameter() + { + $requestContext = new RequestContext(); + $requestContext->setParameters(array('foo' => 'bar')); + + $this->assertEquals('bar', $requestContext->getParameter('foo')); + $this->assertNull($requestContext->getParameter('baz')); + } + + public function testSetParameter() + { + $requestContext = new RequestContext(); + $requestContext->setParameter('foo', 'bar'); + + $this->assertEquals('bar', $requestContext->getParameter('foo')); + } + + public function testMethod() + { + $requestContext = new RequestContext(); + $requestContext->setMethod('post'); + + $this->assertSame('POST', $requestContext->getMethod()); + } + + public function testScheme() + { + $requestContext = new RequestContext(); + $requestContext->setScheme('HTTPS'); + + $this->assertSame('https', $requestContext->getScheme()); + } + + public function testHost() + { + $requestContext = new RequestContext(); + $requestContext->setHost('eXampLe.com'); + + $this->assertSame('example.com', $requestContext->getHost()); + } + + public function testQueryString() + { + $requestContext = new RequestContext(); + $requestContext->setQueryString(null); + + $this->assertSame('', $requestContext->getQueryString()); + } + + public function testPort() + { + $requestContext = new RequestContext(); + $requestContext->setHttpPort('123'); + $requestContext->setHttpsPort('456'); + + $this->assertSame(123, $requestContext->getHttpPort()); + $this->assertSame(456, $requestContext->getHttpsPort()); + } + + public function testFluentInterface() + { + $requestContext = new RequestContext(); + + $this->assertSame($requestContext, $requestContext->setBaseUrl('/app.php')); + $this->assertSame($requestContext, $requestContext->setPathInfo('/index')); + $this->assertSame($requestContext, $requestContext->setMethod('POST')); + $this->assertSame($requestContext, $requestContext->setScheme('https')); + $this->assertSame($requestContext, $requestContext->setHost('example.com')); + $this->assertSame($requestContext, $requestContext->setQueryString('foo=bar')); + $this->assertSame($requestContext, $requestContext->setHttpPort(80)); + $this->assertSame($requestContext, $requestContext->setHttpsPort(443)); + $this->assertSame($requestContext, $requestContext->setParameters(array())); + $this->assertSame($requestContext, $requestContext->setParameter('foo', 'bar')); + } +} diff --git a/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php b/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php new file mode 100644 index 00000000..058a100d --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouteCollectionBuilder; + +class RouteCollectionBuilderTest extends TestCase +{ + public function testImport() + { + $resolvedLoader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $resolver = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderResolverInterface')->getMock(); + $resolver->expects($this->once()) + ->method('resolve') + ->with('admin_routing.yml', 'yaml') + ->will($this->returnValue($resolvedLoader)); + + $originalRoute = new Route('/foo/path'); + $expectedCollection = new RouteCollection(); + $expectedCollection->add('one_test_route', $originalRoute); + $expectedCollection->addResource(new FileResource(__DIR__.'/Fixtures/file_resource.yml')); + + $resolvedLoader + ->expects($this->once()) + ->method('load') + ->with('admin_routing.yml', 'yaml') + ->will($this->returnValue($expectedCollection)); + + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $loader->expects($this->any()) + ->method('getResolver') + ->will($this->returnValue($resolver)); + + // import the file! + $routes = new RouteCollectionBuilder($loader); + $importedRoutes = $routes->import('admin_routing.yml', '/', 'yaml'); + + // we should get back a RouteCollectionBuilder + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollectionBuilder', $importedRoutes); + + // get the collection back so we can look at it + $addedCollection = $importedRoutes->build(); + $route = $addedCollection->get('one_test_route'); + $this->assertSame($originalRoute, $route); + // should return file_resource.yml, which is in the original collection + $this->assertCount(1, $addedCollection->getResources()); + + // make sure the routes were imported into the top-level builder + $this->assertCount(1, $routes->build()); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testImportWithoutLoaderThrowsException() + { + $collectionBuilder = new RouteCollectionBuilder(); + $collectionBuilder->import('routing.yml'); + } + + public function testAdd() + { + $collectionBuilder = new RouteCollectionBuilder(); + + $addedRoute = $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout'); + $addedRoute2 = $collectionBuilder->add('/blogs', 'AppBundle:Blog:list', 'blog_list'); + $this->assertInstanceOf('Symfony\Component\Routing\Route', $addedRoute); + $this->assertEquals('AppBundle:Order:checkout', $addedRoute->getDefault('_controller')); + + $finalCollection = $collectionBuilder->build(); + $this->assertSame($addedRoute2, $finalCollection->get('blog_list')); + } + + public function testFlushOrdering() + { + $importedCollection = new RouteCollection(); + $importedCollection->add('imported_route1', new Route('/imported/foo1')); + $importedCollection->add('imported_route2', new Route('/imported/foo2')); + + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + // make this loader able to do the import - keeps mocking simple + $loader->expects($this->any()) + ->method('supports') + ->will($this->returnValue(true)); + $loader + ->expects($this->once()) + ->method('load') + ->will($this->returnValue($importedCollection)); + + $routes = new RouteCollectionBuilder($loader); + + // 1) Add a route + $routes->add('/checkout', 'AppBundle:Order:checkout', 'checkout_route'); + // 2) Import from a file + $routes->mount('/', $routes->import('admin_routing.yml')); + // 3) Add another route + $routes->add('/', 'AppBundle:Default:homepage', 'homepage'); + // 4) Add another route + $routes->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); + + // set a default value + $routes->setDefault('_locale', 'fr'); + + $actualCollection = $routes->build(); + + $this->assertCount(5, $actualCollection); + $actualRouteNames = array_keys($actualCollection->all()); + $this->assertEquals(array( + 'checkout_route', + 'imported_route1', + 'imported_route2', + 'homepage', + 'admin_dashboard', + ), $actualRouteNames); + + // make sure the defaults were set + $checkoutRoute = $actualCollection->get('checkout_route'); + $defaults = $checkoutRoute->getDefaults(); + $this->assertArrayHasKey('_locale', $defaults); + $this->assertEquals('fr', $defaults['_locale']); + } + + public function testFlushSetsRouteNames() + { + $collectionBuilder = new RouteCollectionBuilder(); + + // add a "named" route + $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); + // add an unnamed route + $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') + ->setMethods(array('GET')); + + // integer route names are allowed - they don't confuse things + $collectionBuilder->add('/products', 'AppBundle:Product:list', 100); + + $actualCollection = $collectionBuilder->build(); + $actualRouteNames = array_keys($actualCollection->all()); + $this->assertEquals(array( + 'admin_dashboard', + 'GET_blogs', + '100', + ), $actualRouteNames); + } + + public function testFlushSetsDetailsOnChildrenRoutes() + { + $routes = new RouteCollectionBuilder(); + + $routes->add('/blogs/{page}', 'listAction', 'blog_list') + // unique things for the route + ->setDefault('page', 1) + ->setRequirement('id', '\d+') + ->setOption('expose', true) + // things that the collection will try to override (but won't) + ->setDefault('_format', 'html') + ->setRequirement('_format', 'json|xml') + ->setOption('fooBar', true) + ->setHost('example.com') + ->setCondition('request.isSecure()') + ->setSchemes(array('https')) + ->setMethods(array('POST')); + + // a simple route, nothing added to it + $routes->add('/blogs/{id}', 'editAction', 'blog_edit'); + + // configure the collection itself + $routes + // things that will not override the child route + ->setDefault('_format', 'json') + ->setRequirement('_format', 'xml') + ->setOption('fooBar', false) + ->setHost('symfony.com') + ->setCondition('request.query.get("page")==1') + // some unique things that should be set on the child + ->setDefault('_locale', 'fr') + ->setRequirement('_locale', 'fr|en') + ->setOption('niceRoute', true) + ->setSchemes(array('http')) + ->setMethods(array('GET', 'POST')); + + $collection = $routes->build(); + $actualListRoute = $collection->get('blog_list'); + + $this->assertEquals(1, $actualListRoute->getDefault('page')); + $this->assertEquals('\d+', $actualListRoute->getRequirement('id')); + $this->assertTrue($actualListRoute->getOption('expose')); + // none of these should be overridden + $this->assertEquals('html', $actualListRoute->getDefault('_format')); + $this->assertEquals('json|xml', $actualListRoute->getRequirement('_format')); + $this->assertTrue($actualListRoute->getOption('fooBar')); + $this->assertEquals('example.com', $actualListRoute->getHost()); + $this->assertEquals('request.isSecure()', $actualListRoute->getCondition()); + $this->assertEquals(array('https'), $actualListRoute->getSchemes()); + $this->assertEquals(array('POST'), $actualListRoute->getMethods()); + // inherited from the main collection + $this->assertEquals('fr', $actualListRoute->getDefault('_locale')); + $this->assertEquals('fr|en', $actualListRoute->getRequirement('_locale')); + $this->assertTrue($actualListRoute->getOption('niceRoute')); + + $actualEditRoute = $collection->get('blog_edit'); + // inherited from the collection + $this->assertEquals('symfony.com', $actualEditRoute->getHost()); + $this->assertEquals('request.query.get("page")==1', $actualEditRoute->getCondition()); + $this->assertEquals(array('http'), $actualEditRoute->getSchemes()); + $this->assertEquals(array('GET', 'POST'), $actualEditRoute->getMethods()); + } + + /** + * @dataProvider providePrefixTests + */ + public function testFlushPrefixesPaths($collectionPrefix, $routePath, $expectedPath) + { + $routes = new RouteCollectionBuilder(); + + $routes->add($routePath, 'someController', 'test_route'); + + $outerRoutes = new RouteCollectionBuilder(); + $outerRoutes->mount($collectionPrefix, $routes); + + $collection = $outerRoutes->build(); + + $this->assertEquals($expectedPath, $collection->get('test_route')->getPath()); + } + + public function providePrefixTests() + { + $tests = array(); + // empty prefix is of course ok + $tests[] = array('', '/foo', '/foo'); + // normal prefix - does not matter if it's a wildcard + $tests[] = array('/{admin}', '/foo', '/{admin}/foo'); + // shows that a prefix will always be given the starting slash + $tests[] = array('0', '/foo', '/0/foo'); + + // spaces are ok, and double slahses at the end are cleaned + $tests[] = array('/ /', '/foo', '/ /foo'); + + return $tests; + } + + public function testFlushSetsPrefixedWithMultipleLevels() + { + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $routes = new RouteCollectionBuilder($loader); + + $routes->add('homepage', 'MainController::homepageAction', 'homepage'); + + $adminRoutes = $routes->createBuilder(); + $adminRoutes->add('/dashboard', 'AdminController::dashboardAction', 'admin_dashboard'); + + // embedded collection under /admin + $adminBlogRoutes = $routes->createBuilder(); + $adminBlogRoutes->add('/new', 'BlogController::newAction', 'admin_blog_new'); + // mount into admin, but before the parent collection has been mounted + $adminRoutes->mount('/blog', $adminBlogRoutes); + + // now mount the /admin routes, above should all still be /blog/admin + $routes->mount('/admin', $adminRoutes); + // add a route after mounting + $adminRoutes->add('/users', 'AdminController::userAction', 'admin_users'); + + // add another sub-collection after the mount + $otherAdminRoutes = $routes->createBuilder(); + $otherAdminRoutes->add('/sales', 'StatsController::indexAction', 'admin_stats_sales'); + $adminRoutes->mount('/stats', $otherAdminRoutes); + + // add a normal collection and see that it is also prefixed + $importedCollection = new RouteCollection(); + $importedCollection->add('imported_route', new Route('/foo')); + // make this loader able to do the import - keeps mocking simple + $loader->expects($this->any()) + ->method('supports') + ->will($this->returnValue(true)); + $loader + ->expects($this->any()) + ->method('load') + ->will($this->returnValue($importedCollection)); + // import this from the /admin route builder + $adminRoutes->import('admin.yml', '/imported'); + + $collection = $routes->build(); + $this->assertEquals('/admin/dashboard', $collection->get('admin_dashboard')->getPath(), 'Routes before mounting have the prefix'); + $this->assertEquals('/admin/users', $collection->get('admin_users')->getPath(), 'Routes after mounting have the prefix'); + $this->assertEquals('/admin/blog/new', $collection->get('admin_blog_new')->getPath(), 'Sub-collections receive prefix even if mounted before parent prefix'); + $this->assertEquals('/admin/stats/sales', $collection->get('admin_stats_sales')->getPath(), 'Sub-collections receive prefix if mounted after parent prefix'); + $this->assertEquals('/admin/imported/foo', $collection->get('imported_route')->getPath(), 'Normal RouteCollections are also prefixed properly'); + } + + public function testAutomaticRouteNamesDoNotConflict() + { + $routes = new RouteCollectionBuilder(); + + $adminRoutes = $routes->createBuilder(); + // route 1 + $adminRoutes->add('/dashboard', ''); + + $accountRoutes = $routes->createBuilder(); + // route 2 + $accountRoutes->add('/dashboard', '') + ->setMethods(array('GET')); + // route 3 + $accountRoutes->add('/dashboard', '') + ->setMethods(array('POST')); + + $routes->mount('/admin', $adminRoutes); + $routes->mount('/account', $accountRoutes); + + $collection = $routes->build(); + // there are 2 routes (i.e. with non-conflicting names) + $this->assertCount(3, $collection->all()); + } +} diff --git a/vendor/symfony/routing/Tests/RouteCollectionTest.php b/vendor/symfony/routing/Tests/RouteCollectionTest.php new file mode 100644 index 00000000..83457ff1 --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteCollectionTest.php @@ -0,0 +1,305 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; + +class RouteCollectionTest extends TestCase +{ + public function testRoute() + { + $collection = new RouteCollection(); + $route = new Route('/foo'); + $collection->add('foo', $route); + $this->assertEquals(array('foo' => $route), $collection->all(), '->add() adds a route'); + $this->assertEquals($route, $collection->get('foo'), '->get() returns a route by name'); + $this->assertNull($collection->get('bar'), '->get() returns null if a route does not exist'); + } + + public function testOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + $collection->add('foo', new Route('/foo1')); + + $this->assertEquals('/foo1', $collection->get('foo')->getPath()); + } + + public function testDeepOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/foo1')); + + $collection2 = new RouteCollection(); + $collection2->add('foo', new Route('/foo2')); + + $collection1->addCollection($collection2); + $collection->addCollection($collection1); + + $this->assertEquals('/foo2', $collection1->get('foo')->getPath()); + $this->assertEquals('/foo2', $collection->get('foo')->getPath()); + } + + public function testIterator() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection1->add('foo', $foo = new Route('/foo-new')); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $this->assertInstanceOf('\ArrayIterator', $collection->getIterator()); + $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'last' => $last), $collection->getIterator()->getArrayCopy()); + } + + public function testCount() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', new Route('/bar')); + $collection->addCollection($collection1); + + $this->assertCount(2, $collection); + } + + public function testAddCollection() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection1->add('foo', $foo = new Route('/foo-new')); + + $collection2 = new RouteCollection(); + $collection2->add('grandchild', $grandchild = new Route('/grandchild')); + + $collection1->addCollection($collection2); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'grandchild' => $grandchild, 'last' => $last), $collection->all(), + '->addCollection() imports routes of another collection, overrides if necessary and adds them at the end'); + } + + public function testAddCollectionWithResources() + { + $collection = new RouteCollection(); + $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml')); + $collection1 = new RouteCollection(); + $collection1->addResource($foo1 = new FileResource(__DIR__.'/Fixtures/foo1.xml')); + $collection->addCollection($collection1); + $this->assertEquals(array($foo, $foo1), $collection->getResources(), '->addCollection() merges resources'); + } + + public function testAddDefaultsAndRequirementsAndOptions() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{placeholder}')); + $collection1 = new RouteCollection(); + $collection1->add('bar', new Route('/{placeholder}', + array('_controller' => 'fixed', 'placeholder' => 'default'), array('placeholder' => '.+'), array('option' => 'value')) + ); + $collection->addCollection($collection1); + + $collection->addDefaults(array('placeholder' => 'new-default')); + $this->assertEquals(array('placeholder' => 'new-default'), $collection->get('foo')->getDefaults(), '->addDefaults() adds defaults to all routes'); + $this->assertEquals(array('_controller' => 'fixed', 'placeholder' => 'new-default'), $collection->get('bar')->getDefaults(), + '->addDefaults() adds defaults to all routes and overwrites existing ones'); + + $collection->addRequirements(array('placeholder' => '\d+')); + $this->assertEquals(array('placeholder' => '\d+'), $collection->get('foo')->getRequirements(), '->addRequirements() adds requirements to all routes'); + $this->assertEquals(array('placeholder' => '\d+'), $collection->get('bar')->getRequirements(), + '->addRequirements() adds requirements to all routes and overwrites existing ones'); + + $collection->addOptions(array('option' => 'new-value')); + $this->assertEquals( + array('option' => 'new-value', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'), + $collection->get('bar')->getOptions(), '->addOptions() adds options to all routes and overwrites existing ones' + ); + } + + public function testAddPrefix() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo')); + $collection2 = new RouteCollection(); + $collection2->add('bar', $bar = new Route('/bar')); + $collection->addCollection($collection2); + $collection->addPrefix(' / '); + $this->assertSame('/foo', $collection->get('foo')->getPath(), '->addPrefix() trims the prefix and a single slash has no effect'); + $collection->addPrefix('/{admin}', array('admin' => 'admin'), array('admin' => '\d+')); + $this->assertEquals('/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() adds a prefix to all routes'); + $this->assertEquals('/{admin}/bar', $collection->get('bar')->getPath(), '->addPrefix() adds a prefix to all routes'); + $this->assertEquals(array('admin' => 'admin'), $collection->get('foo')->getDefaults(), '->addPrefix() adds defaults to all routes'); + $this->assertEquals(array('admin' => 'admin'), $collection->get('bar')->getDefaults(), '->addPrefix() adds defaults to all routes'); + $this->assertEquals(array('admin' => '\d+'), $collection->get('foo')->getRequirements(), '->addPrefix() adds requirements to all routes'); + $this->assertEquals(array('admin' => '\d+'), $collection->get('bar')->getRequirements(), '->addPrefix() adds requirements to all routes'); + $collection->addPrefix('0'); + $this->assertEquals('/0/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() ensures a prefix must start with a slash and must not end with a slash'); + $collection->addPrefix('/ /'); + $this->assertSame('/ /0/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() can handle spaces if desired'); + $this->assertSame('/ /0/{admin}/bar', $collection->get('bar')->getPath(), 'the route pattern of an added collection is in synch with the added prefix'); + } + + public function testAddPrefixOverridesDefaultsAndRequirements() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo.{_format}')); + $collection->add('bar', $bar = new Route('/bar.{_format}', array(), array('_format' => 'json'))); + $collection->addPrefix('/admin', array(), array('_format' => 'html')); + + $this->assertEquals('html', $collection->get('foo')->getRequirement('_format'), '->addPrefix() overrides existing requirements'); + $this->assertEquals('html', $collection->get('bar')->getRequirement('_format'), '->addPrefix() overrides existing requirements'); + } + + public function testResource() + { + $collection = new RouteCollection(); + $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml')); + $collection->addResource($bar = new FileResource(__DIR__.'/Fixtures/bar.xml')); + $collection->addResource(new FileResource(__DIR__.'/Fixtures/foo.xml')); + + $this->assertEquals(array($foo, $bar), $collection->getResources(), + '->addResource() adds a resource and getResources() only returns unique ones by comparing the string representation'); + } + + public function testUniqueRouteWithGivenName() + { + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/old')); + $collection2 = new RouteCollection(); + $collection3 = new RouteCollection(); + $collection3->add('foo', $new = new Route('/new')); + + $collection2->addCollection($collection3); + $collection1->addCollection($collection2); + + $this->assertSame($new, $collection1->get('foo'), '->get() returns new route that overrode previous one'); + // size of 1 because collection1 contains /new but not /old anymore + $this->assertCount(1, $collection1->getIterator(), '->addCollection() removes previous routes when adding new routes with the same name'); + } + + public function testGet() + { + $collection1 = new RouteCollection(); + $collection1->add('a', $a = new Route('/a')); + $collection2 = new RouteCollection(); + $collection2->add('b', $b = new Route('/b')); + $collection1->addCollection($collection2); + $collection1->add('$péß^a|', $c = new Route('/special')); + + $this->assertSame($b, $collection1->get('b'), '->get() returns correct route in child collection'); + $this->assertSame($c, $collection1->get('$péß^a|'), '->get() can handle special characters'); + $this->assertNull($collection2->get('a'), '->get() does not return the route defined in parent collection'); + $this->assertNull($collection1->get('non-existent'), '->get() returns null when route does not exist'); + $this->assertNull($collection1->get(0), '->get() does not disclose internal child RouteCollection'); + } + + public function testRemove() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $collection->remove('foo'); + $this->assertSame(array('bar' => $bar, 'last' => $last), $collection->all(), '->remove() can remove a single route'); + $collection->remove(array('bar', 'last')); + $this->assertSame(array(), $collection->all(), '->remove() accepts an array and can remove multiple routes at once'); + } + + public function testSetHost() + { + $collection = new RouteCollection(); + $routea = new Route('/a'); + $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setHost('{locale}.example.com'); + + $this->assertEquals('{locale}.example.com', $routea->getHost()); + $this->assertEquals('{locale}.example.com', $routeb->getHost()); + } + + public function testSetCondition() + { + $collection = new RouteCollection(); + $routea = new Route('/a'); + $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net', array(), array(), 'context.getMethod() == "GET"'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setCondition('context.getMethod() == "POST"'); + + $this->assertEquals('context.getMethod() == "POST"', $routea->getCondition()); + $this->assertEquals('context.getMethod() == "POST"', $routeb->getCondition()); + } + + public function testClone() + { + $collection = new RouteCollection(); + $collection->add('a', new Route('/a')); + $collection->add('b', new Route('/b', array('placeholder' => 'default'), array('placeholder' => '.+'))); + + $clonedCollection = clone $collection; + + $this->assertCount(2, $clonedCollection); + $this->assertEquals($collection->get('a'), $clonedCollection->get('a')); + $this->assertNotSame($collection->get('a'), $clonedCollection->get('a')); + $this->assertEquals($collection->get('b'), $clonedCollection->get('b')); + $this->assertNotSame($collection->get('b'), $clonedCollection->get('b')); + } + + public function testSetSchemes() + { + $collection = new RouteCollection(); + $routea = new Route('/a', array(), array(), array(), '', 'http'); + $routeb = new Route('/b'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setSchemes(array('http', 'https')); + + $this->assertEquals(array('http', 'https'), $routea->getSchemes()); + $this->assertEquals(array('http', 'https'), $routeb->getSchemes()); + } + + public function testSetMethods() + { + $collection = new RouteCollection(); + $routea = new Route('/a', array(), array(), array(), '', array(), array('GET', 'POST')); + $routeb = new Route('/b'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setMethods('PUT'); + + $this->assertEquals(array('PUT'), $routea->getMethods()); + $this->assertEquals(array('PUT'), $routeb->getMethods()); + } +} diff --git a/vendor/symfony/routing/Tests/RouteCompilerTest.php b/vendor/symfony/routing/Tests/RouteCompilerTest.php new file mode 100644 index 00000000..54006d7e --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteCompilerTest.php @@ -0,0 +1,389 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCompiler; + +class RouteCompilerTest extends TestCase +{ + /** + * @dataProvider provideCompileData + */ + public function testCompile($name, $arguments, $prefix, $regex, $variables, $tokens) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + } + + public function provideCompileData() + { + return array( + array( + 'Static route', + array('/foo'), + '/foo', '#^/foo$#s', array(), array( + array('text', '/foo'), + ), + ), + + array( + 'Route with a variable', + array('/foo/{bar}'), + '/foo', '#^/foo/(?P[^/]++)$#s', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with a variable that has a default value', + array('/foo/{bar}', array('bar' => 'bar')), + '/foo', '#^/foo(?:/(?P[^/]++))?$#s', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with several variables', + array('/foo/{bar}/{foobar}'), + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with several variables that have default values', + array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')), + '/foo', '#^/foo(?:/(?P[^/]++)(?:/(?P[^/]++))?)?$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with several variables but some of them have no default values', + array('/foo/{bar}/{foobar}', array('bar' => 'bar')), + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with an optional variable as the first segment', + array('/{bar}', array('bar' => 'bar')), + '', '#^/(?P[^/]++)?$#s', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + ), + ), + + array( + 'Route with a requirement of 0', + array('/{bar}', array('bar' => null), array('bar' => '0')), + '', '#^/(?P0)?$#s', array('bar'), array( + array('variable', '/', '0', 'bar'), + ), + ), + + array( + 'Route with an optional variable as the first segment with requirements', + array('/{bar}', array('bar' => 'bar'), array('bar' => '(foo|bar)')), + '', '#^/(?P(foo|bar))?$#s', array('bar'), array( + array('variable', '/', '(foo|bar)', 'bar'), + ), + ), + + array( + 'Route with only optional variables', + array('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar')), + '', '#^/(?P[^/]++)?(?:/(?P[^/]++))?$#s', array('foo', 'bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('variable', '/', '[^/]++', 'foo'), + ), + ), + + array( + 'Route with a variable in last position', + array('/foo-{bar}'), + '/foo-', '#^/foo\-(?P[^/]++)$#s', array('bar'), array( + array('variable', '-', '[^/]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Route with nested placeholders', + array('/{static{var}static}'), + '/{static', '#^/\{static(?P[^/]+)static\}$#s', array('var'), array( + array('text', 'static}'), + array('variable', '', '[^/]+', 'var'), + array('text', '/{static'), + ), + ), + + array( + 'Route without separator between variables', + array('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '(y|Y)')), + '', '#^/(?P[^/\.]+)(?P[^/\.]+)(?P(y|Y))(?:(?P[^/\.]++)(?:\.(?P<_format>[^/]++))?)?$#s', array('w', 'x', 'y', 'z', '_format'), array( + array('variable', '.', '[^/]++', '_format'), + array('variable', '', '[^/\.]++', 'z'), + array('variable', '', '(y|Y)', 'y'), + array('variable', '', '[^/\.]+', 'x'), + array('variable', '/', '[^/\.]+', 'w'), + ), + ), + + array( + 'Route with a format', + array('/foo/{bar}.{_format}'), + '/foo', '#^/foo/(?P[^/\.]++)\.(?P<_format>[^/]++)$#s', array('bar', '_format'), array( + array('variable', '.', '[^/]++', '_format'), + array('variable', '/', '[^/\.]++', 'bar'), + array('text', '/foo'), + ), + ), + + array( + 'Static non UTF-8 route', + array("/fo\xE9"), + "/fo\xE9", "#^/fo\xE9$#s", array(), array( + array('text', "/fo\xE9"), + ), + ), + + array( + 'Route with an explicit UTF-8 requirement', + array('/{bar}', array('bar' => null), array('bar' => '.'), array('utf8' => true)), + '', '#^/(?P.)?$#su', array('bar'), array( + array('variable', '/', '.', 'bar', true), + ), + ), + ); + } + + /** + * @group legacy + * @dataProvider provideCompileImplicitUtf8Data + * @expectedDeprecation Using UTF-8 route %s without setting the "utf8" option is deprecated %s. + */ + public function testCompileImplicitUtf8Data($name, $arguments, $prefix, $regex, $variables, $tokens, $deprecationType) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + } + + public function provideCompileImplicitUtf8Data() + { + return array( + array( + 'Static UTF-8 route', + array('/foé'), + '/foé', '#^/foé$#su', array(), array( + array('text', '/foé'), + ), + 'patterns', + ), + + array( + 'Route with an implicit UTF-8 requirement', + array('/{bar}', array('bar' => null), array('bar' => 'é')), + '', '#^/(?Pé)?$#su', array('bar'), array( + array('variable', '/', 'é', 'bar', true), + ), + 'requirements', + ), + + array( + 'Route with a UTF-8 class requirement', + array('/{bar}', array('bar' => null), array('bar' => '\pM')), + '', '#^/(?P\pM)?$#su', array('bar'), array( + array('variable', '/', '\pM', 'bar', true), + ), + 'requirements', + ), + + array( + 'Route with a UTF-8 separator', + array('/foo/{bar}§{_format}', array(), array(), array('compiler_class' => Utf8RouteCompiler::class)), + '/foo', '#^/foo/(?P[^/§]++)§(?P<_format>[^/]++)$#su', array('bar', '_format'), array( + array('variable', '§', '[^/]++', '_format', true), + array('variable', '/', '[^/§]++', 'bar', true), + array('text', '/foo'), + ), + 'patterns', + ), + ); + } + + /** + * @expectedException \LogicException + */ + public function testRouteWithSameVariableTwice() + { + $route = new Route('/{name}/{name}'); + + $compiled = $route->compile(); + } + + /** + * @expectedException \LogicException + */ + public function testRouteCharsetMismatch() + { + $route = new Route("/\xE9/{bar}", array(), array('bar' => '.'), array('utf8' => true)); + + $compiled = $route->compile(); + } + + /** + * @expectedException \LogicException + */ + public function testRequirementCharsetMismatch() + { + $route = new Route('/foo/{bar}', array(), array('bar' => "\xE9"), array('utf8' => true)); + + $compiled = $route->compile(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRouteWithFragmentAsPathParameter() + { + $route = new Route('/{_fragment}'); + + $compiled = $route->compile(); + } + + /** + * @dataProvider getVariableNamesStartingWithADigit + * @expectedException \DomainException + */ + public function testRouteWithVariableNameStartingWithADigit($name) + { + $route = new Route('/{'.$name.'}'); + $route->compile(); + } + + public function getVariableNamesStartingWithADigit() + { + return array( + array('09'), + array('123'), + array('1e2'), + ); + } + + /** + * @dataProvider provideCompileWithHostData + */ + public function testCompileWithHost($name, $arguments, $prefix, $regex, $variables, $pathVariables, $tokens, $hostRegex, $hostVariables, $hostTokens) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, str_replace(array("\n", ' '), '', $compiled->getRegex()), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($pathVariables, $compiled->getPathVariables(), $name.' (path variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + $this->assertEquals($hostRegex, str_replace(array("\n", ' '), '', $compiled->getHostRegex()), $name.' (host regex)'); + $this->assertEquals($hostVariables, $compiled->getHostVariables(), $name.' (host variables)'); + $this->assertEquals($hostTokens, $compiled->getHostTokens(), $name.' (host tokens)'); + } + + public function provideCompileWithHostData() + { + return array( + array( + 'Route with host pattern', + array('/hello', array(), array(), array(), 'www.example.com'), + '/hello', '#^/hello$#s', array(), array(), array( + array('text', '/hello'), + ), + '#^www\.example\.com$#si', array(), array( + array('text', 'www.example.com'), + ), + ), + array( + 'Route with host pattern and some variables', + array('/hello/{name}', array(), array(), array(), 'www.example.{tld}'), + '/hello', '#^/hello/(?P[^/]++)$#s', array('tld', 'name'), array('name'), array( + array('variable', '/', '[^/]++', 'name'), + array('text', '/hello'), + ), + '#^www\.example\.(?P[^\.]++)$#si', array('tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', 'www.example'), + ), + ), + array( + 'Route with variable at beginning of host', + array('/hello', array(), array(), array(), '{locale}.example.{tld}'), + '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( + array('text', '/hello'), + ), + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#si', array('locale', 'tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', '.example'), + array('variable', '', '[^\.]++', 'locale'), + ), + ), + array( + 'Route with host variables that has a default value', + array('/hello', array('locale' => 'a', 'tld' => 'b'), array(), array(), '{locale}.example.{tld}'), + '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( + array('text', '/hello'), + ), + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#si', array('locale', 'tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', '.example'), + array('variable', '', '[^\.]++', 'locale'), + ), + ), + ); + } + + /** + * @expectedException \DomainException + */ + public function testRouteWithTooLongVariableName() + { + $route = new Route(sprintf('/{%s}', str_repeat('a', RouteCompiler::VARIABLE_MAXIMUM_LENGTH + 1))); + $route->compile(); + } +} + +class Utf8RouteCompiler extends RouteCompiler +{ + const SEPARATORS = '/§'; +} diff --git a/vendor/symfony/routing/Tests/RouteTest.php b/vendor/symfony/routing/Tests/RouteTest.php new file mode 100644 index 00000000..b65dfb54 --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteTest.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Route; + +class RouteTest extends TestCase +{ + public function testConstructor() + { + $route = new Route('/{foo}', array('foo' => 'bar'), array('foo' => '\d+'), array('foo' => 'bar'), '{locale}.example.com'); + $this->assertEquals('/{foo}', $route->getPath(), '__construct() takes a path as its first argument'); + $this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '__construct() takes defaults as its second argument'); + $this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '__construct() takes requirements as its third argument'); + $this->assertEquals('bar', $route->getOption('foo'), '__construct() takes options as its fourth argument'); + $this->assertEquals('{locale}.example.com', $route->getHost(), '__construct() takes a host pattern as its fifth argument'); + + $route = new Route('/', array(), array(), array(), '', array('Https'), array('POST', 'put'), 'context.getMethod() == "GET"'); + $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes schemes as its sixth argument and lowercases it'); + $this->assertEquals(array('POST', 'PUT'), $route->getMethods(), '__construct() takes methods as its seventh argument and uppercases it'); + $this->assertEquals('context.getMethod() == "GET"', $route->getCondition(), '__construct() takes a condition as its eight argument'); + + $route = new Route('/', array(), array(), array(), '', 'Https', 'Post'); + $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes a single scheme as its sixth argument'); + $this->assertEquals(array('POST'), $route->getMethods(), '__construct() takes a single method as its seventh argument'); + } + + public function testPath() + { + $route = new Route('/{foo}'); + $route->setPath('/{bar}'); + $this->assertEquals('/{bar}', $route->getPath(), '->setPath() sets the path'); + $route->setPath(''); + $this->assertEquals('/', $route->getPath(), '->setPath() adds a / at the beginning of the path if needed'); + $route->setPath('bar'); + $this->assertEquals('/bar', $route->getPath(), '->setPath() adds a / at the beginning of the path if needed'); + $this->assertEquals($route, $route->setPath(''), '->setPath() implements a fluent interface'); + $route->setPath('//path'); + $this->assertEquals('/path', $route->getPath(), '->setPath() does not allow two slashes "//" at the beginning of the path as it would be confused with a network path when generating the path from the route'); + } + + public function testOptions() + { + $route = new Route('/{foo}'); + $route->setOptions(array('foo' => 'bar')); + $this->assertEquals(array_merge(array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ), array('foo' => 'bar')), $route->getOptions(), '->setOptions() sets the options'); + $this->assertEquals($route, $route->setOptions(array()), '->setOptions() implements a fluent interface'); + + $route->setOptions(array('foo' => 'foo')); + $route->addOptions(array('bar' => 'bar')); + $this->assertEquals($route, $route->addOptions(array()), '->addOptions() implements a fluent interface'); + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'), $route->getOptions(), '->addDefaults() keep previous defaults'); + } + + public function testOption() + { + $route = new Route('/{foo}'); + $this->assertFalse($route->hasOption('foo'), '->hasOption() return false if option is not set'); + $this->assertEquals($route, $route->setOption('foo', 'bar'), '->setOption() implements a fluent interface'); + $this->assertEquals('bar', $route->getOption('foo'), '->setOption() sets the option'); + $this->assertTrue($route->hasOption('foo'), '->hasOption() return true if option is set'); + } + + public function testDefaults() + { + $route = new Route('/{foo}'); + $route->setDefaults(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '->setDefaults() sets the defaults'); + $this->assertEquals($route, $route->setDefaults(array()), '->setDefaults() implements a fluent interface'); + + $route->setDefault('foo', 'bar'); + $this->assertEquals('bar', $route->getDefault('foo'), '->setDefault() sets a default value'); + + $route->setDefault('foo2', 'bar2'); + $this->assertEquals('bar2', $route->getDefault('foo2'), '->getDefault() return the default value'); + $this->assertNull($route->getDefault('not_defined'), '->getDefault() return null if default value is not set'); + + $route->setDefault('_controller', $closure = function () { return 'Hello'; }); + $this->assertEquals($closure, $route->getDefault('_controller'), '->setDefault() sets a default value'); + + $route->setDefaults(array('foo' => 'foo')); + $route->addDefaults(array('bar' => 'bar')); + $this->assertEquals($route, $route->addDefaults(array()), '->addDefaults() implements a fluent interface'); + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar'), $route->getDefaults(), '->addDefaults() keep previous defaults'); + } + + public function testRequirements() + { + $route = new Route('/{foo}'); + $route->setRequirements(array('foo' => '\d+')); + $this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '->setRequirements() sets the requirements'); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->getRequirement() returns a requirement'); + $this->assertNull($route->getRequirement('bar'), '->getRequirement() returns null if a requirement is not defined'); + $route->setRequirements(array('foo' => '^\d+$')); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->getRequirement() removes ^ and $ from the path'); + $this->assertEquals($route, $route->setRequirements(array()), '->setRequirements() implements a fluent interface'); + + $route->setRequirements(array('foo' => '\d+')); + $route->addRequirements(array('bar' => '\d+')); + $this->assertEquals($route, $route->addRequirements(array()), '->addRequirements() implements a fluent interface'); + $this->assertEquals(array('foo' => '\d+', 'bar' => '\d+'), $route->getRequirements(), '->addRequirement() keep previous requirements'); + } + + public function testRequirement() + { + $route = new Route('/{foo}'); + $this->assertFalse($route->hasRequirement('foo'), '->hasRequirement() return false if requirement is not set'); + $route->setRequirement('foo', '^\d+$'); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->setRequirement() removes ^ and $ from the path'); + $this->assertTrue($route->hasRequirement('foo'), '->hasRequirement() return true if requirement is set'); + } + + /** + * @dataProvider getInvalidRequirements + * @expectedException \InvalidArgumentException + */ + public function testSetInvalidRequirement($req) + { + $route = new Route('/{foo}'); + $route->setRequirement('foo', $req); + } + + public function getInvalidRequirements() + { + return array( + array(''), + array(array()), + array('^$'), + array('^'), + array('$'), + ); + } + + public function testHost() + { + $route = new Route('/'); + $route->setHost('{locale}.example.net'); + $this->assertEquals('{locale}.example.net', $route->getHost(), '->setHost() sets the host pattern'); + } + + public function testScheme() + { + $route = new Route('/'); + $this->assertEquals(array(), $route->getSchemes(), 'schemes is initialized with array()'); + $this->assertFalse($route->hasScheme('http')); + $route->setSchemes('hTTp'); + $this->assertEquals(array('http'), $route->getSchemes(), '->setSchemes() accepts a single scheme string and lowercases it'); + $this->assertTrue($route->hasScheme('htTp')); + $this->assertFalse($route->hasScheme('httpS')); + $route->setSchemes(array('HttpS', 'hTTp')); + $this->assertEquals(array('https', 'http'), $route->getSchemes(), '->setSchemes() accepts an array of schemes and lowercases them'); + $this->assertTrue($route->hasScheme('htTp')); + $this->assertTrue($route->hasScheme('httpS')); + } + + public function testMethod() + { + $route = new Route('/'); + $this->assertEquals(array(), $route->getMethods(), 'methods is initialized with array()'); + $route->setMethods('gEt'); + $this->assertEquals(array('GET'), $route->getMethods(), '->setMethods() accepts a single method string and uppercases it'); + $route->setMethods(array('gEt', 'PosT')); + $this->assertEquals(array('GET', 'POST'), $route->getMethods(), '->setMethods() accepts an array of methods and uppercases them'); + } + + public function testCondition() + { + $route = new Route('/'); + $this->assertSame('', $route->getCondition()); + $route->setCondition('context.getMethod() == "GET"'); + $this->assertSame('context.getMethod() == "GET"', $route->getCondition()); + } + + public function testCompile() + { + $route = new Route('/{foo}'); + $this->assertInstanceOf('Symfony\Component\Routing\CompiledRoute', $compiled = $route->compile(), '->compile() returns a compiled route'); + $this->assertSame($compiled, $route->compile(), '->compile() only compiled the route once if unchanged'); + $route->setRequirement('foo', '.*'); + $this->assertNotSame($compiled, $route->compile(), '->compile() recompiles if the route was modified'); + } + + public function testSerialize() + { + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + + $serialized = serialize($route); + $unserialized = unserialize($serialized); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } + + /** + * Tests that the compiled version is also serialized to prevent the overhead + * of compiling it again after unserialize. + */ + public function testSerializeWhenCompiled() + { + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + $route->setHost('{locale}.example.net'); + $route->compile(); + + $serialized = serialize($route); + $unserialized = unserialize($serialized); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } + + /** + * Tests that the serialized representation of a route in one symfony version + * also works in later symfony versions, i.e. the unserialized route is in the + * same state as another, semantically equivalent, route. + */ + public function testSerializedRepresentationKeepsWorking() + { + $serialized = 'C:31:"Symfony\Component\Routing\Route":934:{a:8:{s:4:"path";s:13:"/prefix/{foo}";s:4:"host";s:20:"{locale}.example.net";s:8:"defaults";a:1:{s:3:"foo";s:7:"default";}s:12:"requirements";a:1:{s:3:"foo";s:3:"\d+";}s:7:"options";a:1:{s:14:"compiler_class";s:39:"Symfony\Component\Routing\RouteCompiler";}s:7:"schemes";a:0:{}s:7:"methods";a:0:{}s:8:"compiled";C:39:"Symfony\Component\Routing\CompiledRoute":569:{a:8:{s:4:"vars";a:2:{i:0;s:6:"locale";i:1;s:3:"foo";}s:11:"path_prefix";s:7:"/prefix";s:10:"path_regex";s:30:"#^/prefix(?:/(?P\d+))?$#s";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:3:"foo";}i:1;a:2:{i:0;s:4:"text";i:1;s:7:"/prefix";}}s:9:"path_vars";a:1:{i:0;s:3:"foo";}s:10:"host_regex";s:39:"#^(?P[^\.]++)\.example\.net$#si";s:11:"host_tokens";a:2:{i:0;a:2:{i:0;s:4:"text";i:1;s:12:".example.net";}i:1;a:4:{i:0;s:8:"variable";i:1;s:0:"";i:2;s:7:"[^\.]++";i:3;s:6:"locale";}}s:9:"host_vars";a:1:{i:0;s:6:"locale";}}}}}'; + $unserialized = unserialize($serialized); + + $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+')); + $route->setHost('{locale}.example.net'); + $route->compile(); + + $this->assertEquals($route, $unserialized); + $this->assertNotSame($route, $unserialized); + } +} diff --git a/vendor/symfony/routing/Tests/RouterTest.php b/vendor/symfony/routing/Tests/RouterTest.php new file mode 100644 index 00000000..409959ee --- /dev/null +++ b/vendor/symfony/routing/Tests/RouterTest.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Routing\Router; +use Symfony\Component\HttpFoundation\Request; + +class RouterTest extends TestCase +{ + private $router = null; + + private $loader = null; + + protected function setUp() + { + $this->loader = $this->getMockBuilder('Symfony\Component\Config\Loader\LoaderInterface')->getMock(); + $this->router = new Router($this->loader, 'routing.yml'); + } + + public function testSetOptionsWithSupportedOptions() + { + $this->router->setOptions(array( + 'cache_dir' => './cache', + 'debug' => true, + 'resource_type' => 'ResourceType', + )); + + $this->assertSame('./cache', $this->router->getOption('cache_dir')); + $this->assertTrue($this->router->getOption('debug')); + $this->assertSame('ResourceType', $this->router->getOption('resource_type')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the following options: "option_foo", "option_bar" + */ + public function testSetOptionsWithUnsupportedOptions() + { + $this->router->setOptions(array( + 'cache_dir' => './cache', + 'option_foo' => true, + 'option_bar' => 'baz', + 'resource_type' => 'ResourceType', + )); + } + + public function testSetOptionWithSupportedOption() + { + $this->router->setOption('cache_dir', './cache'); + + $this->assertSame('./cache', $this->router->getOption('cache_dir')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the "option_foo" option + */ + public function testSetOptionWithUnsupportedOption() + { + $this->router->setOption('option_foo', true); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the "option_foo" option + */ + public function testGetOptionWithUnsupportedOption() + { + $this->router->getOption('option_foo', true); + } + + public function testThatRouteCollectionIsLoaded() + { + $this->router->setOption('resource_type', 'ResourceType'); + + $routeCollection = $this->getMockBuilder('Symfony\Component\Routing\RouteCollection')->getMock(); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', 'ResourceType') + ->will($this->returnValue($routeCollection)); + + $this->assertSame($routeCollection, $this->router->getRouteCollection()); + } + + /** + * @dataProvider provideMatcherOptionsPreventingCaching + */ + public function testMatcherIsCreatedIfCacheIsNotConfigured($option) + { + $this->router->setOption($option, null); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', null) + ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Routing\RouteCollection')->getMock())); + + $this->assertInstanceOf('Symfony\\Component\\Routing\\Matcher\\UrlMatcher', $this->router->getMatcher()); + } + + public function provideMatcherOptionsPreventingCaching() + { + return array( + array('cache_dir'), + array('matcher_cache_class'), + ); + } + + /** + * @dataProvider provideGeneratorOptionsPreventingCaching + */ + public function testGeneratorIsCreatedIfCacheIsNotConfigured($option) + { + $this->router->setOption($option, null); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', null) + ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Routing\RouteCollection')->getMock())); + + $this->assertInstanceOf('Symfony\\Component\\Routing\\Generator\\UrlGenerator', $this->router->getGenerator()); + } + + public function provideGeneratorOptionsPreventingCaching() + { + return array( + array('cache_dir'), + array('generator_cache_class'), + ); + } + + public function testMatchRequestWithUrlMatcherInterface() + { + $matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock(); + $matcher->expects($this->once())->method('match'); + + $p = new \ReflectionProperty($this->router, 'matcher'); + $p->setAccessible(true); + $p->setValue($this->router, $matcher); + + $this->router->matchRequest(Request::create('/')); + } + + public function testMatchRequestWithRequestMatcherInterface() + { + $matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\RequestMatcherInterface')->getMock(); + $matcher->expects($this->once())->method('matchRequest'); + + $p = new \ReflectionProperty($this->router, 'matcher'); + $p->setAccessible(true); + $p->setValue($this->router, $matcher); + + $this->router->matchRequest(Request::create('/')); + } +} diff --git a/vendor/symfony/routing/composer.json b/vendor/symfony/routing/composer.json new file mode 100644 index 00000000..38069dc4 --- /dev/null +++ b/vendor/symfony/routing/composer.json @@ -0,0 +1,56 @@ +{ + "name": "symfony/routing", + "type": "library", + "description": "Symfony Routing Component", + "keywords": ["routing", "router", "URL", "URI"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/config": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.3" + }, + "suggest": { + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/yaml": "For using the YAML loader", + "symfony/expression-language": "For using expression matching", + "doctrine/annotations": "For using the annotation loader", + "symfony/dependency-injection": "For loading routes from a service" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Routing\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/routing/phpunit.xml.dist b/vendor/symfony/routing/phpunit.xml.dist new file mode 100644 index 00000000..bcc09595 --- /dev/null +++ b/vendor/symfony/routing/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/web/.htaccess b/web/.htaccess new file mode 100644 index 00000000..d52f4206 --- /dev/null +++ b/web/.htaccess @@ -0,0 +1,10 @@ + + Options -MultiViews + + RewriteEngine On + #RewriteBase /var/www/teste-ecs + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [QSA,L] + FallbackResource index.php + diff --git a/web/index.php b/web/index.php new file mode 100644 index 00000000..e2834ecb --- /dev/null +++ b/web/index.php @@ -0,0 +1,117 @@ +getMessage(); + } + + /** + * Start listing of videos + */ + $app->get('/videos', function (Request $request) use ($app, $dbh) { + + $accessToken = $request->query->get("access_token"); + + if( $accessToken ) { + $userData = getUserData($accessToken,$app,$dbh); + + if( $userData ) { + $videoTitle = $request->query->get("title"); + + $sql = 'SELECT id, title, description, duration FROM video WHERE active=?'; + $params = array("yes"); + + if( $videoTitle ){ + $sql .= " AND title LIKE ?" ; + $params[] = "%".$videoTitle."%"; + } + + $sth = $dbh->prepare($sql); + $sth->execute($params); + $videoRs = $sth->fetchAll(PDO::FETCH_ASSOC); + + if(empty($videoRs)) { + return new Response("Nenhum resultado encontrado para a busca: '". $request->query->get("title")."'", 404); + } + + return $app->json($videoRs); + } + } + + return new Response("Access Token inválido: ", 404); + + }); + + $app->post('/videos', function(Request $request) use ($app, $dbh) { + $insertData = json_decode($request->getContent(), true); + + $sth = $dbh->prepare('INSERT INTO video ( title,category_id,description,filename,duration,active,creation_date) + VALUES(:title, :category_id, :description,:filename,:duration,:active,NOW())'); + + $sth->execute($insertData); + + $response = new Response('Ok', 201); + return $response; + }); + + + function getUserData ( $accessToken, $app,$dbh ) { + $sql = "SELECT user.* FROM user + LEFT JOIN user_access_token ON ( user_access_token.user_id =user.id ) + WHERE user.active=? AND + user_access_token.active=? AND + user_access_token.access_token=? "; + + $params = array("yes" , "yes" , $accessToken ); + + $sth = $dbh->prepare($sql); + + $sth->execute($params); + + $userRs = $sth->fetchAll(PDO::FETCH_ASSOC); + + + if(!empty($userRs)) { + + return $app->json($userRs); + } + + return false; + } + + $app->put('/videos/{title}', function(Request $request, $title) use ($app, $dbh) { + $videoData = json_decode($request->getContent(), true); + $dados['title'] = #title; + + $sth = $dbh->prepare('UPDATE video + SET title=:title, description=:description, duration=:duration + WHERE id=:id'); + + $sth->execute($videoData); + return $app->json($videoData, 200); + })->assert('title', '\w+'); + + // DELETE - excluir + $app->delete('/video/{title}', function($title) use ($app, $dbh) { + $sth = $dbh->prepare('DELETE FROM video WHERE title = ?'); + $sth->execute([ $title ]); + + if($sth->rowCount() ==0) { + return new Response("Vídeo nçao encontrado", 404); + } + + return new Response(null, 204); + })->assert('title', '\w+'); + + $app->run(); \ No newline at end of file From 7e924abd1688fb6eed6e28857162bd014e70d9dc Mon Sep 17 00:00:00 2001 From: thiagoeramos Date: Wed, 21 Jun 2017 23:57:12 -0300 Subject: [PATCH 2/3] Ajuste Aplication Thiago --- web/index.php | 87 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/web/index.php b/web/index.php index e2834ecb..cd85ba9e 100644 --- a/web/index.php +++ b/web/index.php @@ -53,19 +53,44 @@ }); + /** + * Criando um vídeo + * + */ $app->post('/videos', function(Request $request) use ($app, $dbh) { $insertData = json_decode($request->getContent(), true); - $sth = $dbh->prepare('INSERT INTO video ( title,category_id,description,filename,duration,active,creation_date) - VALUES(:title, :category_id, :description,:filename,:duration,:active,NOW())'); - - $sth->execute($insertData); + + $accessToken = $request->query->get("access_token"); + + if( $accessToken ) { + $userData = getUserData( $accessToken , $app , $dbh ); + if ( $userData ) { + $sth = $dbh->prepare( + 'INSERT INTO video ( title,category_id,description,filename,duration,active,creation_date) + VALUES(:title, :category_id, :description,:filename,:duration,:active,NOW())' + ); + $sth->execute( $insertData ); + $response = new Response( 'Ok' , 201 ); + + return $response; + } + + } + + return new Response("Access Token inválido: ", 404); + - $response = new Response('Ok', 201); - return $response; }); - + /** + * User info + * + * @param $accessToken + * @param $app + * @param $dbh + * @return bool + */ function getUserData ( $accessToken, $app,$dbh ) { $sql = "SELECT user.* FROM user LEFT JOIN user_access_token ON ( user_access_token.user_id =user.id ) @@ -90,28 +115,34 @@ function getUserData ( $accessToken, $app,$dbh ) { return false; } + /** + * User Edit + */ $app->put('/videos/{title}', function(Request $request, $title) use ($app, $dbh) { - $videoData = json_decode($request->getContent(), true); - $dados['title'] = #title; - - $sth = $dbh->prepare('UPDATE video - SET title=:title, description=:description, duration=:duration - WHERE id=:id'); - - $sth->execute($videoData); - return $app->json($videoData, 200); - })->assert('title', '\w+'); - - // DELETE - excluir - $app->delete('/video/{title}', function($title) use ($app, $dbh) { - $sth = $dbh->prepare('DELETE FROM video WHERE title = ?'); - $sth->execute([ $title ]); - - if($sth->rowCount() ==0) { - return new Response("Vídeo nçao encontrado", 404); - } - - return new Response(null, 204); + $videoData = json_decode($request->getContent(), true); + + + + $accessToken = $request->query->get("access_token"); + + if( $accessToken ) { + $userData = getUserData( $accessToken , $app , $dbh ); + if ( $userData ) { + $sth = $dbh->prepare( + 'UPDATE video + SET title=:title, description=:description, duration=:duration + WHERE id=:id' + ); + $sth->execute( $videoData ); + + return $app->json( $videoData , 200 ); + } + } + + + return new Response("Access Token inválido: ", 404); + })->assert('title', '\w+'); + $app->run(); \ No newline at end of file From 801134286d1cc8f8d7ffbd85cfb88a1baf1be092 Mon Sep 17 00:00:00 2001 From: Thiago Ramos Date: Thu, 22 Jun 2017 15:13:36 -0300 Subject: [PATCH 3/3] Adicionando inserts --- .htaccess | 9 +++++++++ composer.phar | Bin 0 -> 1844814 bytes db/ThiagoEvangelistaRamos.sql | 5 +++++ web/index.php | 8 +++----- 4 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 .htaccess create mode 100644 composer.phar diff --git a/.htaccess b/.htaccess new file mode 100644 index 00000000..317d963b --- /dev/null +++ b/.htaccess @@ -0,0 +1,9 @@ + + Options -MultiViews + + RewriteEngine On + #RewriteBase /var/www/teste/ + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [QSA,L] + diff --git a/composer.phar b/composer.phar new file mode 100644 index 0000000000000000000000000000000000000000..80004edab035ebbe43d62c2461bd418abcf16967 GIT binary patch literal 1844814 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%0An!UjD7h*)oN5$aji@hn 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 + echo 'Error DB: ' . $e->getMessage(); } /** @@ -59,8 +59,7 @@ */ $app->post('/videos', function(Request $request) use ($app, $dbh) { $insertData = json_decode($request->getContent(), true); - - + $accessToken = $request->query->get("access_token"); if( $accessToken ) { @@ -79,7 +78,6 @@ } return new Response("Access Token inválido: ", 404); - });

^Y=^AMbZuJ?{gL6K^)0TriL%AT$2v|}ne{VTZGSdS>C zpW^m9Tm>}R>!Gxr=b`BbS~$SH#sVgK6DW#?6ijBUR+C~=36|*R^fBPc1$Qm7do3b8 z=BPx`t^&x=kcfB6uv`xlNwor=!m82#J)cUvpD0;IHf67P-Nfm2XuOjt@+?sN_yk%~ zSi#j{XMc!||A;w`b+?u-U*A62K5>m}9j4!GpInzy96O#lXKgf}(=DQAt#t_rm~q-e zqwMTq>t!^w>#odNHNUCf&GY>^u6rGhiwkCqWxbLAyDrr=inel9ju(`xeK$Vc!ys$F zAejf59M03SD`@;57ah~X*>WRQUy9xHF-_|DWvan2WU#E2x)q}E(44;kGlVg`l=COq zlQDK$zNXm=n9ZDb(Yu8!BNpb6ZE=X~N_!kXz#RcPwu>|0N_qx;{4~ArBl75W%W>(+ z*>Ddx-NtMBmyssV}Z+KL>SF(o?Z{hOVi) zqMd$KTanx=hd5Hj)U2;cHG{!ljgNj{I%p`Vk43g^1jq(=Y~ZqNQjiovW5VpCryFYz zU%uG*+s@|N`u&aj_!Btv1UwptBnTpzpDl;#lYWL>cZsyHcWk>^`B*lLAm&*6$FmRQ zGeEt=x4gTR)DSN`=g3@g*DLd1NnU)}dM!EU)lKdtmwI;#DA0_v*w9*fc{#F7amIqt zX`Xk2ts;l#-30Nh2)%&Z;oa&^{l2h%pg3Yu3Edt+Hp$N2$kgTB`^YGsPCtD2 z#S^;8b!eYWx;OUaWmRooG=?1YawPqZR#Zw!wg^oYm%VN_OsIUKrV3H)f(605d8y}} zMfCJKsNh@nGkN z`y2n+`Az?R@%-7}(4QZrxtLRLdwn>dHIW%o4Hx$*ZWb15ZgqcnI5;^O9&JeO60swh z$*30c@X7t>&)1&qJl`a7*&*{YjJeDbAt%G>%AS7OzrB15v1tZ25S9y1#;QJ=10E*qMraaLVle(2w=;H@{SObLZ*v$1mpT&pJb?WCH-1GrV9^oMeN zGZCkdGQ4HNNnfu1WRQvmtMvZFA5szQOy^-0nJ~7HRKqGoJ&1Y z0l+VuQMeCfhm?B(BysE8NKirEmli(NV>6a~;h)R)O{#FxN@;O{zJ$fLzIK13W7Lau z_l}>treN&sqrww&Shd7yd9y<*3Tl~xG#$z^On87lWf^}Pz!o7+rsID#YiU^j=@>;} z0va6Ss4w@+v7C<(_YnTxq4?YZknl*j>}g-Po1N>M^7T(YJX<@$J#8n`>o+^ko<6|u zo#!vsHa1>tK%R+8)Z#pHNC{)ji}DF}4=2+%dUl}}^*S-ZJ`NM?j!)kXPnJ79-^BtF zt`cpN&VPL|`L87kTfu?okYLaOCZhAAJ5(2>vIM&EGNJes*~&Eb{2oO;d=-Ae;uwGh z!*3TAFrxO23C^tEP%o{3aXsx0R3z8+l|d{=gpn0 zv^}PP5!89N(A`+ue0h8MD@kC^x`i#S@9wm`L-h)#kFx)W!oo ztAgvkM8!p%alp-19cGfP4lkoLaquX(J4N7W7bvnP^ihM4t*)Km!#)j^V1g}SA_{fv zYSnfnfnW+LDd1;yXmsU5jcU5cl&x7w>k&j+iy;zqY{KarATm50|FjO%h(O^C=^ikA zG69Cs{hQU*XpeJV(bG^tza+ic7E(54@FGnQUxEa&64y^AtJp2`^$PyH!T%x4@?Rnj z47K$adZWMVe)N6l@7>qeu^>}41GVCcC9Y@+y$k^A^>K0llkezn$7tw+fQ9?y(O+ay zeXkUc?dAbCXgp2pCz4@NX~iY$haK4>s~q#v7AkTK51>>h3zt#^Ld^vWduWzRGR>vV z-Hu#>bK(BuY~tiu6UfGMqLlkGE^n}8%Fa(2H(ZMDa+d!ViZAMvR`uspzqmidEyMe8 z#Ng_B>OoLrn-cU5N2|_L%fIXybV0^lSDWEc7}vdz8B-Q|SOtVFI=3f=^q@pgx5#ag zMQ@gMSaLagtbRgHR#F7CN`WMQigNNY9-_=-EHsJ7*u^y~G=l^}!#Y4`{Btf5!L4bs zy~U?dOh_NfsE{hFybqe(cmhE#LIU#U?ciWo(U@!i5bQODC#FGLp(?=m&m9h!`n?^HRAwj%VLn@C12yEPN$oX5^4WDmSip{=Ihe^*0wq=#} zBEIVzoS?zcXb*RN*H|~af(OUE$J0l{$=-M*w-95=qDYc4ux|k)w=~!X5xbmgaVI}A@R%h`;o6U%u&*~zCd0=BCRwsv#*a%ni=C-3>ZiXR5bRQ zTFq1hCzd;TJ})s9-lsMe#}(N*iyMT|5REiJF7Mxc2cob>;qritbhH`WPKGAqZU4%; zrmL5N+m^tHrtGM20+QE_ zOA6uT=`T~t->NIH@Hs~97zW0oCl<+l$%gq(t}_TBA?ubcgQiZtQ7fi@clUgBwErj= z8M;rvIA-if*XW~WVJ_>f2u4}eBC~D#|1gW}q3+76R#r;>Zit4U4Gn|FFI((0NU5NL z#JBwuT?&E5;8s|PkqP%LT*w&9A|ariMssYhl+_-^s2BidkNJB?RXP`7Ljx)w{B1HT zob8oh%Ln$Hv4j|}UI|Va1wz)Fdsp}b8u+4i+uQ&(Ems7?Z0fz0?lLy3p2g(vWmYJH zb!N8x2nOo~G_rR?kw)Z*tu)W(Tti;fa8=u)!SKFoBLM$^&zna zlcyY3WQf~u8NR|phpqu_194n2*GddIGX*F7ik4eMd-~?HnZp6_i8pFk!9Jx#aaxna?QTi}?ev_xk9Kj{}U#nM(K2oa3Fa&?Z zuJJ4BQlzQwcI{NCfok?g?1f@K{({)luacgnMI0zJNrq#SJ-VD~zMo?PFR=zSUlPR}^GXShoRN|mqv|7ANeK;U{@CD%`(yrB zqC5=^LQd%SAe8W3LMHy2OX+`yr4AqPZTKN*t{E09yuI34i-HLfT59SHkQi&< zS}U+(b=p?06f!xR@*4J|Z4VW6 zn=UnU2Z+LB7;fD(Pt4XoA3o0H1R9&?wK8Ca@S)XChcD`;Go!!L3A)pHP6pVj*7jZ7 zae^ltRvIACdZ}-rk0^0b$POM^0-G!oR}p%Vk!0Bd=VostN!YUZki06v2WN@cKp!VP z>w^UIZoIm-uwfHv>*i;0NELFnBB>!~trt*L5O7hGXHN}w7T!n%ouEI+B$r~+HQ70cy7UeE$iNESr_wqFkKD`Sh+V0#k!4g+LNC%t~5VprZT`qXWS!Zge8V5)kO0JQOT4KKlS{Re7PLZjA2Nil> z!HTa9-%QHE!M%v{A!yM&{F*oUEC8Uo`==oQT1~D#f}2CckVu3`&MY>?lI6{ixXsv{np6L@Mtm=x;-Gp2c6=?#8;;85)+PZBMW3a__A;7VA+o znAu69`_Or}(OQUKMuO${6`@KhpCxuCcNP0lc4BqriYzj82rH)KoAo<$Zq|=SC%Vzr{rU0W7yYSw za^yqsnoNDqqXbqT%htBV&7xW1I}t+m>wfM*M$O=l;2K+iwW+JfD?l9P~#EfZ@F zpN|c?k(yx%@de<3%L@R<)^h^)?QGy=YzHng!EL3O?01T6|5!Pfl7Q0)coAsOrmkwO ztWQ)aMiZh^>~A>gB&#<^ELpvvfi1ux`}JCEt7AesBacQ4!pTrWo|o%ZlqF3ER{}AW z9bTvaxioW!W0?k;yGaZx_cZ1S%kS>>w)a2YzWLSfOTF#o?foy8@F4$JbD6CdfZ+8O zMNHdcDpY&Eb^G!c6`l{_N|jCc6#1Xe*Lt^*xJi>U*J>PY*Mh3M*ayrckNb60g76 zsKryuf=t?#*^tSrBr7s`>d`Yg+#Hofl5-YONWcR^60q>glQMZbQ=S@%m&x$><->AG z%#KTXCM_mEq4J%J)AZqV^w7j$U=IO1_H_vq+mz2jKMA;Om;4M(d=7l?e+z{Ik7Wem z!hx{*J`%I1CQOp7MhhH|VnS|BAK643gEgDQ`qp$*#cX45&os-prjxLQjo)r~GH=4% zw%+NQTtU#tMk@Ig9)sP{5$?}QmSgRz7jIBa1!YS$335SG6Lb-8Vn{Pg< zQf=B(D@~*gYZfB!yQQa12dk!&06)LK=!yV~*-@0%YEGNOW2lV0q!P^teg0!m|DRTs zX(OC(lheK14>e$}sVzm7Cga>~-aUFWoDS`G(W;?GrJ`*5a?c;G-aM1CFXQdvchRqp z{tQ>A6|W3c(W?g#V?skVW<%YFtPMVvnsV90$AdkD_rM|XKTWk*htn<2Ipm?<&+D{a ztoD>|ba;#~;mHyBFuamV1mTxi7cd8f9>R0D6zi)_y#sfjj^#`z=G4a6VlhxXcuzBn zOWo`gO9|x@mKjXV+UGZs^nQ%m39Qqj6&^9f8*Y@mGCJ%>oUvbBO@)kYi>~G7W4I^& zcN(V=P%i)_He%&5hVsG$xSy-?S_A|a-~sOk{0`NtfZ84W*24ZuBtC-f)-0rI-@xIA zYDRh~E2049OWs?FZ4hBg%51$lK`kpe3i~5Bj%%KY-|PAzfArot7-9K3*kx#i2Ewj~ zRB6sur=n)MiCBJ*YD!vQqB3jv4^f9A*ass?uO!{MLXsttl|q#rV~W*Py|cJn(cnNv z`7tKzfU9EH2>}8g+oq=QXoJ2SE4F}O^J`{wi8XC-X8eHu8TZO$COv;Xr$E_gm|#yR zpyUim5^+wbQ4kSC{rs{OvJAHJQo5(CXb6=2ieuX4;&biQ$iy0BdUO}^+@VcB6Yah< zU^%q3Ln>ykQdBD@__ZG)PJ0fgM`CSJTpTEDrp-#Q!)=r0q3W93AM45(^Xx-;4{>=3 zpY4l?b~mfZw}ui4HBxh(LO_}>VbLztcbqb4RDsWE2iS?b`7=wFmhHU#NM=2&=2BCp zNd+uhDp`me#2W1%w&N=N#n{T@XStU%ZP$YmWl_4JG2fPh@OKR?5F(Tg69$yDR&>W^z14_<7p0c=RWBrV2E#Tn{3NIRtp7!8mb zu_&Y<;yLC6fRLMl>=aW~7(7vLDW@;04nqTXA=@XZc4PXE=wL<>v5u7{bVr2vtd!mv z?)MO=Lcn%%#7ni2!D1aigYhrPm%vH?SE8-1e>6CL$Eo&@4KvK}@cRk`bz{>6@4cnp z`|$w(^7P=>>nb@l>ktzc`$yD;mAVMtTzN>nhCQCx2(c;Dz!3@)WK9L-bR?!MEySj4 zCvL_1)yw845~m;`G34fZ&m{ zP>@(7c}IPSfd&jRajWvq7M6KA^DOK_+tPvwDgggJCy9fmOT7JSEnHMU<`SVk5#D)} zIVyTfrjL|;t^9u6`bWAERL2(y>CMAN@x$1ojGS>QsI{?SGN6`+-Aj4Om5u6j)RyJ$ zBMzl`Gv=U)j}?N@kM?`SyT+AE+qf+Pnm{I8=&as08wL(Vio|Z(!Ag^u>gS^ASs3f9-~D`QW49j4pkG* zH;z!-L`9wgq z)6ur{KKpkk!>N#;3jL$e?szc%Ap7Do-2zCH)D7+57KU-!M2bMC-@DUT&4z{^ybqpWjE7b=tso?x`#8EEIW_ptge4g>6=T_Vh%8!#wyo z@lWA0D4T8gNs7R$WecpavM3kJ{w&*UYH=}$J13>G#fBML;*}ATi!GAuM@N~_jfdm3 zg%}0yEX%W!icOhX;HLZe1C(Qn!C%~UNsO4u(rN`KZONE6@O3nRHrA-UBoVc?hSd!7 zG#Ti3`8f6l?;{!{VZ$%|f4F;o@B~HHj~XulAi|p6!t2U6+xBm$v|qS# zz1fet;mh6MW!s~36jt3{mw%c%6o>C7_yhH@?M?yn+a3+35VUo7ZQaj(w^&*bD5`?3 zKfC|(@r#Wgymk=E+kU_H6C@C7ZuRc1VrB8a{cpbB9Zi4hkIvqG^IQMd>EyS*AaCj3 zZ-*m2C` z!Zk8Vz6%94Yx*QfM@yt7~+pNMFR%P~zP11gsT&xVpOj?A7;ApYPmXf4T!l$mY`*&mo!7 z2XT$|b;!@0KG^^m6l%gYgu?On&qlUZh2-glQvdxkY1!wojHu};xl`C{yRInsE(;>N zQn1N(Ft@~8p8EePu#$xQ-kfxFV(!WM)nI6T=U9dSfPmizp>%uqPe1pze*XWy>_a#$ z-&p(;51uXF#{VXO5^0~XmSVvjvZ$(h4>Hx!(<27KG^<-M2;2H40*s(}l!)XMQT?BQ zDzm1`gU`7jRN0)=qqPUGz8CWuFGO6oK&JA120?j0a{+m?gF{${xP1sp7AozaGx>15 zi&CP^z^h6Tp5+{nr#(8MPmPoL`^!xfN`NQBe)zngi`r&|*R$t@Mi-gU-?6ppygXza zqO&=8Gn}dsUArFW8cPDg@I`t0{N>K()4#8Yf%O0QEWY`=Z4#njx^p(gSru^B>M9ytgWpu^wL^C- zf_R?7d}x~}&5POdWG{2hR4DN2I!3iZsm;X4UUlAj@7MmCAFLsP!SQM?UK}5_GdVaB zEgCfHPr1G#{m{({t}{_NI`N}dRAYYNw~GXfll{SXpO|}iI>xp~6eUz#;$O#Q8b^T1 z@VC8zpNOeK?F2yeQf{tmDG9N6kdMWeV%kj{y@6>@~)TJ;j61_&wq5q0W=7Q)IgaGo%J3`cY(3w;Xsaome7f1 zBj%e>W&xfBU6Yh6AYD`?cKZAZp%aK9T%4*~=s(BPYu=}NclHo8p!bFUMw^1ac1+F4 zB4iW5P-*+5E^_b&neS)>`gq6g%kQ5)d=YV2`RZgUFcDHt>*>Q+Fw`?Xhm3l*gXg{s z3}%pl3pAC@Am_r3%A_|?CxZE^3~R|1A_K6HiB5NMZ2?k<1s&nr{@oEAqVg@$n|KoKZ8`ejXUz=XSZ-8= zszbP4nDFSu%a?199BU=wJu+0YU@`}D(d|&0?qXxfqQ}@*;Sm1G=H|2Co`Kkg_+#nb z_T)xy>sJ5Ymto2Dwg&xQU*CX5Be}$3x8KuzV3#|VryQpmCzd#j)M8On zkoiIjMKa2-H)Nbe$wkR}3HRyX!g1O^*pp((!QlAAfZ%{cHZ!6k7xbiQzjJ%3bE9+T zn?Eyhmo>p@vC}ci!257p{sHom`<;LP25VnVVY!R{%X$G&H#;xZ*Pa7&zA!s4*M53Q z(n+&N0d!rnauLsv6jDb4=?coiZEw=dB)-hbe7LdZ0AOr8wP=)eem>f-?e*}?f zfbr;KrNjaw8)!8e3Yo>b6~iE1WRhXfC1DGQukgZbv|w~TdGg~AzF;Z<8#X#=L-hYg zV6GyHbMkB2>7UP0BRub8vZL(b;;;7I%(DPZfVc4l^mpv*+1 zkaz1OWp@h63VV&`vqXd=t*BuRu;u$^6@-aI9MpSSO0?>TWMhv$V7597MKVb=^!~@8 z2~kN&(M)K^K~yo}KFgqNYv(II!RP_QJk8?rR8Z%Qh&N*vbT)P<7hM|azV(N)DMg`QN9re+h??%0d9R%DV$Exe5Z$@tPTe< zHVblLH&vqot?&X`sUj^JCIm_}bCvH8$1-(6Z5n49KYS*BlV%Aax-pDXG>u@)_wBD+KcU-0a6 zh3PS{6jKL+YaNaqU+1N_*F{5Pw{$2Q0guVZ_dtpC$1!_jf{5^JKUjGs? zuy-`C`bjHsMO+-8U8WNt(bD*8I_|+YWOpyeRYS^PcYB-bpwa(`_mU3g0D~jyDG+u@ zEa}Sfudt@rh$kl{8slNt{iH;MwexD@8GLK;!IB=KScY#J7b$Y|c()h0V2XOFok0hV zH{5aQk(}h}@?~D0hBTBwYTb{3mzLo9F81^3NH;q;b$eUrO0IEAh}&svbrpJiGUU(n z%d40-Sfqa5D9YHGo_2PJn5JBaEl7Zu^`yh}+YUd3M{3adkIfg)JEyz<4G)#g+GVex z(}kSwDJWeUWD67huUx7mAb`76oWaK~f;SOkxY<3h;|!Svr2G6rccA|1a06nASHzbc zDFTEXW(e2V%}lXs1~KW?`ls_2ONb|7jHGiv&Lg{;@v%GK z6LeFmIiiqCGems~>0}N>m_vmKo%9M+00y0cPTOT@cq|`{co6Rgoj){?Y^*VFPrBr&>jC*U={m{ZWYc&!YVdM0~2Ii^z;(##jK=49XydDhc-3N%^j z_Tz!J@N#>OtA8Vkf+-e@At(am*b}-Ci#AO6!^5M&WD@fSsEj=154XGzBPU>ajwn8o zJzP_F@Aep3t?lW@y~&)Nm}$_|k(vGTc80XE#-6eRt*sNoo%c<>%s(-7a(KRbCH))? zuAraEI|<9yt^53wdUHCNjE+8iQYb5o(A?he(|bBR`1C#yb{f5bQ?03|`}TfbDuh+u z)D`nf^KM*%fookL`G!%2T9m=5aEqNJDkJ2*jf0BS%ij4r$&Z7Qi;>Z!tpr8kz8wA% zHXyD#P%;!IAd)y#BZ}phn5|8x)_UFRGB%YPr0(?&icg#$?Tdv5TU4%V!bP*8%?D(Q zz=4X>ph!);**QHq`T%Qff^lKXCavDX(39iBs$xXZ#j2)kI6s6NK>~KJW79A5U+!K< zWh5~ncl?eS=@sslnmIrWF2i@p4V~c!e%dO0!Jydc%HHn+=cF$TdGEc-EVoMRK;5)i;t{#3vw4jDSQE!qjdS_ zh4+WthWIf%;t>9Hj&2@w0c05-q&wn4;!0XqiXz#7$llF%Qeofa{ZO|;UvfXIO(L$m zE7^L<{j0LKp$U~jf*wR^CJZLln18O2*c|bXVoUZ)!2;0^_1VQPx&Sw3?ibT}H95s* ziZ5WvAV9Gcs5ao*JGwN)Rkc6qK%b+++FGAZf++uP5ccffIJEntVOxd&%7{!BAK~f)p#p+j=P~D(2uoBx zB^}UU_OZ)>8C+1$wdL(JOmG5A0XGgW;!d#f2LN}BE0>5VF&IB>X+O7Tt(kN(3Ttq{ z`I426Hq$VmBG*hv_Cb44TMe>$<-}(&8&rMk~dewZpy$7we4TDU}VlygrOu3R;Qtu~yEKJ?5DhjULu+oWEL3@PFW95}2I_IBGT=5BDYd#!huMgu%=uW$CT15(t`G zDNsp%6%)pIy=Vf0Rp=W_izGnNIwczFj8g@GL4O#1U|c&b0Z2M1$=ZPxhBU}%e8UmU z`ah@dPg1Ko+TP!;ydR8DV0PbCdl4=t`Vh$R?bwF!g-m#J^uus^c)E}FG|iISKYfm~ zaqJFeb0V2#PY$2);8@0qy@>M$F>y>C%OJ`-LA`#l#bT96T>$H{1N@Y)M8)8UYU3Me z^&CB;5nbKUol&bKf!-j*I5VAx9`zG4_0YutW;b$Sw!*g}rM2FT@be{5D$?{;F zNLh*{VHW@-OH(IR3dSNaNZTk{%28l)g;B)m2F`UAP|y)xH}?Z*pXC-u@2}&L*tVg6 z+;-GS#um#I z0iKHvw+*ikm^HHtA0k&8wN0~Uk02u?d3wBEy;Qqs%UI90$A5}dO~;(&wMcQF7{pJ0y3kvLpTE3n2xa*F_zXCad(Pp40j%K~4}sOV=pILO-~>-z-5=MNc)6%2y1G{Or8OrY_PUP26x`o-qGpF@Cm|QNr$AYWHhKH$uL&TVlk3;@Af7yq2ln6-xTR| z4o@!7IKlSpWcsGJphb^B z2RNX(oZ)`u&ho95FmH(bl`r`J_r5?RA)u`hNu;kGNuJq}y?Jjzmj<nBWbO4kP| zGpu{FFSNt;kl5Qo_y6E(A71XGz8_8jK)bZcqScCF%=ICH=8y=JzrvaS0+j_0AHoWx zh~RCAVeWR)1=Q=MUkk>VjOS5`)P2{?G3Mju#cmts{4>{Yd(r%tnQ=3 zDUMZCM~WgY`N$q70GY(X{F7#ry<;|UJ$pfC_(} z1vL!S@SewAng$TqID}sSq>CGL%2oo6ZfO#D)UH<%mb6o|7c@!CXn!iK?jmT9a}~+_ zs`8_L0)N7&<#c*>X~dN*mBJAznme zY{$_}6jMDM@eSaz1U;RS3EbqWP#5Ni@?=d^0_dYg z`4J?9@sM@VB*O%c+NBAv_t*^~%oe5B%?{%`Ax`IW(hQHdrM7|U0Ta+`3|`0$2A0ea z=q)m-4(uHV|8&(bqkl!JTNbrMQ?;{h*VH6fz+8p57L}3o!-Q8`xSVCju6BZz9+mC@cP$g=8zt4Q zm=s8B`Pb3eB}mFwy~Jc!x=RoUh}y7*cN6Hx^J76Lil?<;(QK?wwU)IT8Haj|8#r~M z>sr&eO7sGM0;M`n@{*H&hUa3N$&WxVHy4_AmADZWiV}yIW!krI-NNOt=|)p8)>@V@ zwEc|(>{zj7F(y#)9tlMYFj!%@GM%Q|x7Q{isvA>UAoigAC3t!dV zwU87^G|Q6 zQb-G_N2~jjH;1vSMv`}1KEb^|x-jYcho6)DjUx`R(VX1;T)dzY`6IX?j?};pdbGBI z_P9UD&b$a(%m#-uF{$yQwCO8!S=LIyy##YrKUemuu!zIyY@+r<^>PSE#dcSksGvnu zQrg3Trh^T0UxBhk2+-Kjv-f5kz)=P--0Eu5Z;$oasha$}Wi?vi2Q7o9WlREN&@6i3 zE^8MLasWYB11qF4X#EKXBTAqncxkIWE^d6G!9_vz^{uZ|X=zRUhe#trkP&s0@aA^S z8xRR1BFN|Jm3Klw1IGgY1GpjSg4}~0*BKn(Ob?>X8oazEdwGt1&0u3#84{JQOZ!GV zTRM@jrIZ5Uiy2z_cR3DP{5llJr=unLhN5VW^)Q!10?=q8p*qBV5b3etY`OGF=k_Ho z%q9ru zZ&8TgbbR;m0Q43BlvBR%%QF?c(~{M+ZBZs{&-x{ayULk z96epRwuP%jwsL-x@F~3NVkRv?666B6cDn@P=C64+wY1IuupW)_XnAmqr3NJd zFXJUe5SUyM5wW^Dnn=C`k2<&J z<0w^9ulo?zTwdWqyAKbAm3^LA*g>z*_qc8#bVU%K2Q0m`N@&s}#acdmkJ`taQm1iL}jSg^Y z#Bg7iXto?E?aHv|V6$cPXT;BQSrivwZqJBXC!&h_n+ZPd3|c2;kkxr&5V>D3N35LI3~#!J#+ z?Z>&u#)n>t6cNN&Dv(5-UW7{KM3exUV#T@J0mC9;-I6^;0#yUPXsNkg1^vE+V@*_> zG76rkvC31~D(0&;2O4c;33_7Have(NaBdDIR!9B9aA;pU1IJx(WvPd=wVugu9r_MC{gcj}&Tk!5bUEVR zxAFJj{oBrV_oF1?brx^`zKuu?CaWT%Y1TeHn4R0$D{5d+v7jHKi!5sRckG&b-B&Lk zqWE9SvF(SfafvSb zeDoYZ!oiWjfy6QJuT0Wb3_l64?aL}sgRfg~5`|m?UVL{afcTFTKa0Dkr*Dr35T~6dAOm0_pnMCq^7Hl$@oiw~l)mgk;a;EY)}sC;$k#tp{ChL5f&VS}-1(T>YDh_U+}Rr9a6<9vCH+IU!}S9Ov4Ke!247 zwryLlks+Es*U&`VRTevR|BT;bc4<4v`gOZ~MR)UT`{%I3l&z9~Zc8A@(KRz+j6Nwr z)^lrFT&{At07}`;yOZKr;%&P{db1H7O_3b~3|Dd{qRv0m?B4l)G!3DI!pT!ShY*Og znK-7K>>QIpxnDTJL+yL52xhh7=Qszl{N=qRIRm2SKN>}dP@fzG;^j(pH!Q=LDjgo1Z%1cm z3||C(LnV%BXJ3yM!7!i~OX+8KxHlkeFi2um_T^fKFeGW>7@3&pj+DVfwJxBn&CM25H`wvgS`^ab-1m*Le-_o2FD zN2!1GA}nwflKzvO{?Y-Kg$5=5;m;Xif$XH6)`SkwSi4bsoe%8y&y+bgd{maN>9%PFK5jS?-{AWC0_ zJ&J0)#Y@R4ekDD}a-iwwbrA{RAohcuAdJK;ATf7LV7WOUAwdD7A{2k*Z6|BEDhCs~ zOsD{+S{eheg{18rtU33;m+^woc9w3widaDdoR93tFOO= zea^_xmr@ryu6!}u=BfdVa_FfPsmB`R6zGe_1$~}Kh8n>eilNzL;^*+KIunn!Ai96N z%d(1Z7)igrdL6P2j!^vb3i?T5K7hm={xaa5a@;CV2`rj<}RKAy&Kmeok z`==UX86cnnuP4}@Wp$-Yz-00sx2&+2J@j&V+#x(3a9KpBlw(on@Rz%|=gEZ7A}_uqgt+5to@nAx<>RD5|g&f)?J)#EDC`RzFR-HE8fm|vs($RCS8NPlpqq+R=jd3?Dv*}l>Fvd5c#M0)5)7(q`lx8W%{&}=sW z(Mc|j^4yt`V>sAaRj(|R*k^V{W7V)m|5ASFBxN{wSHGC7w#V$CCWu_9 z@kJ-IHJZ;{&Askly32o@FB&d@{$2C%&f$^vRmI8wH$aMf*vB2JF&2YWEu8Oh!cpop zhVW{nTI#75v4l~pGKn|By`f3-@#|Z^FTs29X0k%?Rw$@gec|WrkK4a5skuyFcAdcD z2U{kj?r2hfEWuDKsQ5*TczO_&VS|ow@hei(e?Z#Lxfc3GqE7VB!7jw>yH5XIt9FUY zp?uh;TH4h^<_h`vbLWQCzF2v(cK=Z)(GjA0xP-Yn;WaHcI!pIDH$IFeFqz?%*pgRKrP zH~zr(?l=4cESw^OGhDj&m)}-a@L+pc|3$H7TE#0feChw3&TuW*Zf6Jr@p)WO#4p$@ zBVky~t-f$Ixf!w`L)nr>-;{gZvG{e~c?C#hOg%V969_(*G6$QgD4Db6s>&LP4PQ8I%IH8guseyKobMKH9Xz?LXJ_uxM=GSTq?YQQFn;o| zv^mpP768cnLO5aYv|3Q9HlH-$#FcydZw{RRf zl5m`rBBChy3zDv}|6f|g9jaEKh>mG|WA%w3Yk5h$iA&w0s%0N`c}0DquOMU-Q_d2L zAYWB?ejQ&&e`gv8!0{K{!)l&?{Q+NpP{X>bu>q1E7(IFa{yoK{i9rdZR2xc=#hXyp zTa&BVm6qeBURyy_`hd2O;>Vp(@UzF$d3>#mi+IRsA1B;#T#B}i4O0I;>`TR|{=E~A z@3AR`Ku9VKMb%kva{6SW$S;xxCB<0`Od)@jZ@FULzWb`f8l^LDL4^z%b?p36L9D#; zNh0iRZsApPEHGF2CHFVx0r~XR6czh93^of6`BtWDgGc1W)W3i}1_fdSpap?RNPsT$ zC~Z=MPcRA3W?c-8N>-2w;O)x0udTn6Ym%S)na^dX;_Qb7w6*Ar2uoF2tCY6q; z7w*Ax_FZ=ws>pAEi2@(A5FEK!j`>bE#)^@Q$q=Vj6u_smg^NHXNtw1T4vL2%wz>iW z(oj%_Lz1O-QBm)kue)q@P0CL*w;7P=^PT(R9mJl+_tchTl^iyQzQ(%X5*f+J zc!O+fVg|=MB#S1)N6f!-w>Fk|=3#td;pp`gWpuDVhayD2^kb)%o{iu08X+6T> zPs9?e227e0v@b*M4ZSIu`vQ}5)wqOEzq}0s>4#cTAlGK%)Fzi9O-M!u7gXvVncOo^ z@dF4*ul$!KUi2@fq~QlohFMva2IRk#XsNk^9Vhc{V)SF8mMc{r0HKdVl!RiMKY@qf zMh>&PqQzkU_i&-v4$B37hdMzp@D~to=Ux=AkX1i_t)9rR^p+*dkT|HlPgdi>0L4rt zcimG6J%`;_M+#^j4zyZMCcu0JhSTRrep|;(B(ID22JrT#6qLwc_7P_(WL~dbqA_yG z)s7uBt+>0W6I}SC&$;qYg(zl>=!!T%D>PufwFkH}>n#s01W>vMhkX*ugr{1KR21H; zCYGdEl8gQnEHN7h0Qt?n`eutKiMp?Gv+r$FP}fEtLHGNXv}Kr-C=(+DeZ`A6*n5)0 zztz>Nb~E||9R;$=Sn=W%=mQs2_*p^}S%BwHKliT2#VGq-_4A>UE?3BQ%8AE5SgOEQ zOdW=o5E#FcI1S}za2w(my^<7^aWTbF3hUgjbA$76ds)xMOOU}!r&^3U=C^_5;VS9x zpE5BS^jiK1TFPgXe{b>5+eD|LuVK>CPQU}t8Il;BEhN_tc7oxADu?TlWWOAp} z1{YOcH3o9is9{FdY;Zfyl`|`vIYa`|`N8ojWzVIi5)@HcoCgFbba};fnLu1v_&nVV z58Qh@Pqvw^MVZJZUl75V|>WdW#p)F4KaI9BjG(3|TZ6ST}e2CgS*$c6Nsn&4c4u!KK z?TJ3n!ynQ_zh{ej%$U8M}x4iNing#D86nrZfE*%v$}c_zv%WDJ6r#y3Cy{8N9!JM3=^sgU6x zSnb@nwJ4fgGpDQrrJ@`FKbKN7_c=<&IZSD#xdLUv&ng!h5_pX)@2@|_=~N&FxBeXB zL=F(NjH~Uj^$^O$UO5sWT@O;!%#|HXC*)a2dYxIrtgU=m4WA%gdUV43>Fl6T05(k$ z&XfY9)~3ixg)oy_L+>dn)}W59zFIwi#L%E~#PYM_PV8bP!@6BDn=?}4m30lY6oo&a z2o&jhRz*J&qap4R=1NM}xxYD-u&WS7?~8r-k&ceA(SolX8TAR3K)Q(}8yxXY3tSfM zRSWmI%uIckEPE{C`vK?+;uXp4C_b<|B;z43o+rhQ`6p%J5M`CZvf^A+m?Upt5l2V< z5IgiCydSu}NAzr@f(2ZqH#k32x#$Y4FK}2?TR}(~pWkJ_H)GA!EDd>2A zUa-{aYF8VwaCuj@nCJ@H4i?AX`p5nK&Xd*ADnlOC?g4a?y6vnKSE<#;uiY->4ieuh zklj?Rsxjx!U*+N0%lZ>RA;Eal%?U6tze8I;0W5kvQvFupA#g;+Rl4YW3e@KEjy`RJ z0>ZOQWzc!6CXomeAS_^pi-DUSdKi$XJUXF|D`^rlaokQ-5ZzN2kr2Y(7{hnjqH^uNfveUDZd61B+sB_Kt1Ac$?=MheK^|WHvcR? z1|Y|?9wCt^MCn?vhc@YzR4_kkWMTbohEh<0Nta0t8H)`uQz5CqS)T}0o=c^e3@qj` zix~x41D#^38fgfyq3CX+;95(wx)oDG^f^tMpy4p>sIo*k)vQE`LD*ZOjdUF{ETGH- zl(%wkxSS2`Ki8?`FnG;@t^TQH0!m?lC!Ito+p>OfqH^cya_#1e)?~;E#f9Yzo9die^H=tyli9eZI5Y; zcmLeEF&-X$cb)aWuS@DVzkt?_hm&lxX8DD@Tv0Q`^?xsR)&1PHRWF<|OI_4-tLR12 zcqQ`|8>MW8i_~4u&V4r6g-FoEuVRb?1yd|KadhO0L=idc!BU z89IFy_bWP%@4K0o&Fc$`ry5<$;#dSOe5Em%>``=z>og)mlv%Y&KQ&?%Mjc`oIJdg9 zvG)DiPn7#`do#Co{ac;tUCpu8ayRSWs&n`*iEu^pY|&C5-nko6Zt)Dej_CyLa5E@= z5hHkwn?fr&xM3;$@Ii1^rgaF+dP*JisJ8QFbv3{a;#a8!nUpbtNaPK7E6Y8H6HehaYljumq1!|;9@uuTppVY)z*C+dCjbvk@ zTaR1Pa za;eC+fDKU!-d_2D@$OtUCuTBp!sUKHXH=uEX1ADPr19%+Fy*wGCC}V@ng6u3KRd$Bg44tALZ%St#L~4Odn`RE_6?@D7?TeT z!kRB%C=208<`6Q7xLBz%>-%vv&9TXFW5N>z6a*=3i|h}Jql`#5 z9IcaH*Bj(9yy9yash%rdA_FYcW4c^FNa7UBOfj=AJ=gV5y`Jx4GXH=|0E)&#T zUGC!aafo&X8xq=ZASZcNOwfrkR0@VDSUX|r?fs)rB`4TZsDuB-oyVcWy0%fx2ANEh zTTnJ^d-6}=nMV{utR>A(B(W9SOpdWS0TUw$b_-a8!d znjb`uU?{{UYG_>BqfdTkaD*a7u#>&1Sm-gQD89I92?lb{Mt6s0i0PtJn%8CGcYZUtjury zaQW~VjbI6iU&L2Fk!w_o)K1!74iaz@ax)UAj|@bg5B@kmmIaI7$Q^O#$L`6Nv^#0% zCIEfnqkXD1&7oFG4VFf2+qzydY{&8CaSg8X%>h^p^?=;eBb5b_?_3!6RK`|lG=CUE zQJcmf4B4UAXsE!kZ=>5tTU|ALO!e1HrqLa(7H(pXB*P%4p=nYS#f6ce8D8tMPkKeG z251b&2y5GJhO5eGtF`&K@j1FPk;Z;ebDgPAe~?2P!(W~EI>}gRaa#i+4gCIznRG#R z*YAbX9r#Tv8g7!DKRc0f2hyY^%R#*8 zwdsC~{|)zZlPE&J>J=itfM&T?)U0s zWcJ5oI6R)P&L3+d!y}nq8Ht-$;I>3P+|j z^?opB@>g>E<37USN2jbsha_5(cBau|d!zFqNgXlyfz z(15JkEGM<8!7hcU^AyEI(tnQJM7Qu;*6M096B}^y`3OiHVa zdxmc_9RW1sXWUy2X+1eV#Cw$jAmLaYy8^bN%B9;@-bsue){D z=@1&Fk=|x#hb+dI#a@Gq^SE-&G(eo;=-)fUqo!NxanoO4%)6w8_7I}=Gu{|ojia{fo6$(_$wE7ZXW1q zMr)O9cw~d-zEOK!Y}!&DdU!ZEIT*sHirNJ+p)%PnRG4JJ!C#;_lzTH$HbKCB_}lo+ z-dA6J_3!-q_v?BgHjZzu=f-c-O&ZQc$<2LY_wm9$=Y>4OPjR8V2i#>Z|3Oze12vMj zld7h2A>+9heG^b!BP`@pD|&QGg|PGOuDOu{~o;tSSG2 z@l*gYI&;CRqOls@4Y3i7g->1)?KLcl{+zuoo+JI`lB|w?mM@UQ+qy`uLj&c6OBFa{ zzfSufGi6*2y*;_{`UdOI*Ar#!v>oX7d50~9qsggb2i`iAcZ&o9ta=+UJzRpASoL>> zi2N*mt&~?=Zqxn}*J+6)A7M?a^gd;+g`rG%Vpn=j!q5_cq7Qa0H@({Y}qo)9p+?4(|^(T=NE#k{en|CQzQ=7UkpOgGW-SZTF5rz zDgUB>*LlnJAJw0!%nvg*HH4l@eRObwVhk*f;~?5_aGKvD34S*3Q|Uaa-r7-U=fe4< z-mBplI*TX$_@4$S*6rDR9;kh|aB%t$xXrKVK#$2Y)SFDkv7l_QT8dL~#>mn$*DcEQ zhj#KxUdahEeWyH+w#*_G!m)If>ZjT?PhSb2*9_(DMV#1s!s}Q{9T*B8NlwT0S@-Xr zef^B5{F5zFe23$gdv7;Uta@ zjR1o26eesnZENCCgjdGx;rHki$o+(!hFjn{GUh*6RU67{Hr8#p85^WBau>IDh9D*1 zmWe+Ir?j}6(&mzVR;-9jG&WA;oqy9Z$V+htaDZTE%JmzO2@;<{5TrAV&dYBr>-WTq znt=<_Q>%0#*%v#YELj85uLS~>5+YS@$*b2=)u!PL2;QB24M{ytd+^Q$#jo(PhuhFo zjiP|H;8%E{vi1P9=^NCvcxbl)aF1QtDZ&nss{)>)fLav68KC|>n~bp!!HxI5F3ul- z!HslOfV<;8-Hbv)rXxJvhhQ7M8EHCy+_Xj_b4iqb)yB?{W3eJ6_r4YTA#P+oi)AQ= z3rpl>CgS#P&vE$M+1E?kyMG$dl*d9PLN%3qq8f%{I4IYCL4hKi0YF>b4;R`UOL%H! zom%D^M890neSVf!1a-*zckN}X!U0tT*;MLtF!Ljt1=4#iH%;6_4%d97;-%&*6)_~{ zD>0`AP`QOQq>8V2$aMauN)DeK{F4qg-<{A8fPeV27{1lQn^8$3vFFfviz!Dwh_;(3 z>)e_{9XW`X?3oJYRGkL`lj1RdALD*88* zP5gJ>ig0=h{++)y*S0svUkV}~!m_7i8XVC#qIMJ!>*t#m&t=i%QE`I2-lPTdq9a$$ zidj4ui@Qrx2(g_w#`WvJ66tPR;xp_u5Wggrj8m_2Gm^Kkk7o|X#jF`9S?`)bl8<}`+ zhaYyZN2GY|PTrmL)nZNZdnuz!Wv|gFeOf~-J@{&{zpZ?RL&miuNL-*5!A7n%(=G^E zRIoZwOQ3OZNaH!yixU;ZF~^qOwH#1K?%pYB9!KlO_$whq`6TN2Kth(!rz7%K56~|= zINwDd+4m?e#NJhX8-fIbXii7(Lkb4}qLO-_h5kvc7rbUZ1CR>S_ghd}4}vWmws47E z+PHU^X@uvJu!DKn%z$ zrMCWER^Uu6U!0EO@fNKOnVT*3-J?gE2(+MASCQ;G8S#~UGkNXb|dtmwb0T$Q+A@OTk<)gb&(!`5lzTZNflqHOj0+2 z7zM+F9SL*vx*va*QenNnAQZB6Z)H)cgr#3vJS6u4X;wXM9FX7xwIEkRpR$G+XyaXc zBKJ;*1SD@A2MO)zY68>N-0;MYmgQO|G%n-E?sKFb;6cZzTU=;j|Kwpq%2_~%r4)+K zqGE|x)ER2#$;p^mR6Psj6>!558YN*T3Y~{9e+|PoyD}2y{l8_^+n1I6R=ci3PGL7Cbty6!Ue5;3ujE3l=TkkC%Wu-?cdzPjD>(b4|X z;Hl{?)FMvhHszL!T?G9dIy$wqR-^u}Q%|zGEdnN5XJ!GEgyg%3WM=-TLQrbzCPy2B zDW+fR{&S4oA3insqJYNK0cg+?_H9dRBE%@nceXOqb~#cZ-3ZnH#RSSlDG1b;(YCU< zR4^_|J6Srm3o5k5C=B!epS^eQi{nVrh5y&5C^2}tvDBcOXFQB82?4U0!GZ(GUN6Y~ zYv@Mw7`oYR&}D4zXMdk3E}2t`j5Ym(tKNB*)&VkO&CBJof zaejFreiYFFH41=kdCv3}-cPu=oQ%w>N9|kU7orJ-S%t*(0kb26N@LgnRJc;4F^)=q z#zEX5kW2umKC&k3NS}DD6+9V1t0a^M_*`tT=CLY!LKzMJqBS)~(y|GXXoUlJ{+J3h z<-~{uv^)xIkEcu%uyQyZpI%-JYH^}Z)OhXmwSR9D-nWRKIATpC z?-l%htF;4zp}nmTven3Rlel*zAhC@d0HshZOD*A2tm0*fk^*Yx!ty+k3KyAxI?(5b zQxI-q(=*XK)FCxQi7_?r@`Tbmao!CP==XApUEyOYMI6;S(4K=ik0&2);@6@D?YH{e z@>IRq_^&{v2a$_Gc$0rUTB& zl?C4L>t=2Sl?$3yB!-ylcp_5MoGm_3aLK`eP5vD{5F*>nUQ!}a2@A28WYUeC;h=!* zbJg-b(nV2&XV>(+P}X%P3K;`fSS1;FQr_lE@(?Np6LlWpgpbYwV7@S^+jTt>bSCo! z^J%Epw6Q)|GK12Hw7)HTC39!lBrGJ>Kk^R98S`ulRvv|0L*yH}=OmI)obIS{(!3)! zjNRZRRS64gudVE@cXytz^7@c+0vB|>gC-)qB1la=FVQ0Azt}78?6+MK42(>?7M2Q#~(IAI2Us6oCc@qxCG88 zk#bF(Dv7RnoE1SXrUlA(4I*FF=bc9U1VJia*|iYUw)`VptTV^0CuMI^Gt)e-VMf(w zN#ul1zotL+0$DFL-zy;l%~fjx9A?b!l_sohg6k|kqrK*n4G8LkvTLi9TT^~p0v#g$ z%^tSGluFor6D4jZsKNjabEF*Q`XxSaG3PY1C^m^%)Fcs{90n*43{gqL{b zs}xg-AY=MSOJ&qIlBP#C^`yvhC-{~pUo`D_Qa~#a(IP3D1m&?*tk!fjvL4})CB$Lk zM6*LUL|^uiW*&$KNM=PW`NWEAw%fp*|vG&QE>yB2*m&X+%Pl3D{+yoKNe#N>T*FL+&cInm|+Z~Um)1hAVXoUQ8 zsS}oUtDsSJ_CT;Uhx*hH3=1G20b^!Z2r32u(ifVToEIqxf<3;NcvmgvgJVdU1nJU7 zHoE8Nn(p_z!Q=z}M`l0^zg1J(XD#Of_8|^YUVsR+IUPa(BJwH&tBGrjoz*snX)UsAY>2*E0TY60h@ztwh#`RHy0q1{&17b;G4v{JeBMfJEOWow)1zE$NmSk#&F zc}zv8R#!0qT0dgmG}+8cOE;=S=s=3rt}5P%xMSK;o{r6R&eBw5J$ZesUG|eW=6ZoxaV+sLZq;~f|vP$T5zxO)R=R^MGtHpYA8a(OsO+t!dJ5H&x zhF5b0D*=VeBEf){YCg$nmll)HC45GqVg>xB2tZoZPYGG7ixFPvK#(zt@u2zj%a`Mr z)A4fDXLflgK>brFbgR2?{?__|Ks(VSh3QeKRmsLM6>Kq<`OI|6#(zK|wuZoeNXLon z>S$i-Uq?<}=g{SL9#@nT|3FS!N;A~95PS&BJwh_6=*Q$na#t_?Ug;<>qeNIlpMDLG zL-+$OVns*`LW*jr?7>SJgm5#h2p2H~NNk7=Db{v>8lIm+2819rg(5(wT(Y`+_H;g0 z-FAz}2d@d+^y#G%BS?BvE!DHLP|8RRdfeW#=^MZTzQkUilW{YaSlbFbp!yYUwU2ob z(ziN=VvPPLOQgN-t0ierw7)`E^fO6YUX|6aUb=uY`+9KW6p2wWLQQ<$gzgwXPagYb zO*70W3NjA|29~QId>R`-vai+2QCdJ7+SUf-eM=v;6;h3(^Uc#Y^Zh*x^V}U?A6|i^P2U z>1I6|2Rtc`VO5?vYYjy)(Lzbfn1$cpDiwA^zy_Y>NcJG*Rh`G7Qd=Og--=x(n1byu z^=%#Yps$ucR0a)LAb_v!N8*wKPW`;U*m?b=KX^MYkBSOjWn%-!W{;Zn*!~gSb$$sQ zFEsRAnmiHNl9!Z*FIAGTS!N|yO#gIab1Ovp-3QFVLK)Dny1Ky zL)u_Jg= zZ`PK8V{ub%yR4L*j2Z7H#fVLW&`8DF!5`;DD^1RRN|j9IqM%0`q{VSSVSbn;CT3+ zVv${F)fLI$_cf1I3uA&I;J>i2(7cj$M|`tx7PIHnf5vm_fBE^(zb@S2EuGfld&*ep zB9+2#%&@DwS!LU;*El$wSDT=^W}=b`bJt|bSg|#alvU>ON$*&YtR~f+Qf6Cg^%qZd z+Ctop-|J&i$T!Okj>=(!7r>7ipBnTiF*Kt+1zQo6lW(zi*%HZyOYq2!SoQR5vo-Rw zA5b+1$9%lU^$`Mbbz5;!D-$r|IW&V@eTD~h>RHn%lxPnv#Tx^IPAj+CBUlWwxK+7A zzpGmqSZEf2S=QtldgjbhRRE8X`LURK?>_#x)s~7cz@cqRl-z)YJHg-(K;3S=92cs~ zlw`KJa}?)#qYswp5)aLa1O`!rLYPJ|Yp88Z+DyzXdY-n~vo?dmDI!2fh`yI#zDdZt z>hJ?oG%?J*8x>&;CZE9cd9*sBS)$f*{jsFyz^!soR&un2EXDVa#-aRn zIhQ9pQtC|;Czbp2vaH8ik1_z}Rx!?;g9KkwfS+t3ZdyK*V=G7x081%C#dgeO`E7BL zCR$n6I$<9b;*7J3VJYqbtITST8n_KgVH6dT@fI=|ItZNzl2aysnc!ly7@$^-nT zru6S8!wY&p#jX~K>pIR;ZyH;rj#G?~+e?MviGKi)ulWw+2vrzDx4r@pcD08KE4Z?O z3p1KfU8?(mndBxYu@aGiLZxRPgpQI3-e}1-LYs<|Z-2Ow>LH9?NBi0Le&rBl&+P?- z1VN1Q57QYH)3Z9=C6TeNF^~vDzOllY=8tj^p^kb%!Mwm?s*5#%@a9^wWP69qi>@=% zWHtf~S<%)pydf}V%wl6o9cL4jMeysVB8P1SSU^`Ni6QSnNwCrk+zzC1Q@r<4 za-NZNWuUD zB;_xm`WtndMT2Pc=uIL>vS0)aMj_=tgN;@``s9sUmZu$RVe0tjM@NV~H7MfT$EilA za8>~A>RUXW5XO*BX2e^}^StL>09`2^$ogB&g#lbPl+Hnsa=6`97EsLhCX%4s7(ysM zKE>Vj$S9hkEo=(X3*>ld1>&|d+2=zx;J=5D6~B9&p-)ygfM83GdPGxOtT23M6DqyM zvGm-i9zAHku~pNOd(zZqI9*d1*0c*-cU`6wXrY9(8?=g@o+1v)C|428Ffc81BvC3# zzXU2GI;J0PPEZ1RLv@|7%x9|j&9Be|kQZZnXT7`ea(8cKbF;hp?Habb1>qOhNM$vh zqq9ELcK>Hhws!X_ZN}4!*^MyHt8EEGUtp&g)@TZO6f`(Qs#LYp=V#vikRx zZ^CZLxNJXx#}m2BM1=A!NXGYl=BBOgtTXa_duQu!>#O+F_M5#RwHr8F2fENdkTJO) zT3q_h>Whu<)f}pedCLA?yBa5zWRKm)-^I6_Bw+ZMs0ClY`Jr#+KQ@t!lD$wB~Hn-FU2uKgv$G7g(kp!XU`=*>FcK68aJyBYg`SAF+%Cz1#47nEk_eXz zzr0&i3-AA_uJpkpEv1X$R$seph%fkyK(c92y#GE0FW5=RU)yW^+K#l7gfL<*kM`(JtdF z(DuGkF&_0on=eSNfmME+GTqcKaClsK&?3U{3^` zFe8EuH}(V2WQpFE+i+tvaHLUClz=Q7j@%pxkF!~FB#7re=j|8U-Sw?a)xvXtgGp|J zRZlg`G_Sf|jE}|)qMdQ=iVhlidqtcy{0$iE<}uP*?QP0HjDq|iV&%*&OuxnG;H{1k03?!RYR(U*J9dnOCIP~sM`MCZ=DTCo%0hs z>QsD%w-W%k@78yAH@04OH`Y>OxBA>)V0wa7e4pYdu}$ycmIK`13_}R<2e3T5HdFN8 zBFPEyhjy9b!}xN-QA1UPG$fM<9;WN@dUABYrNrfHIe320BC>EMNqI3i#nH>NJH#xC zGMoJAJhZ|=Q?7l*t@eapS9C?~U$DQKTThWAVZaHu=hG{5p;!ixztEAHe2x37)tdcR zcr-gypqXhKs3s^A`W2U|Fh@qU`2eM!fhQ6}R?`sN7Md*>EF1Sm(|4M^U*|zTPjC9u z%BXLSyaBEv=0j9 zrY*wwaf4*tQWIOy?;8pXE*QasO(4(fAk)b32iX_(cW>+O>o2>nb~e#a&m^1|;RX)RlO<%T87z%^ z*op^By(7-2D?_C^(Qn~gpE4f@&OR;{iy1_FJVb)Qce|YFaMgvfx?U%4bkB3E~M@ z=RXHqB;FFqny?TUt0n<0A)9a*n?Q6cYFy+M4|3zW8KMqFEs0&~VijG18<%ESGE1RxH7(~H6nLfc}8eBLZCWHUFgxI(u1hpsI7Wp6% zS6!s@|3D@p;$l6jl|{kW0id`&Pv}fZqM1Hu&A~;Jd!c&0|BtZXf z5%1YwOk?&NdfG!I1g1E`{SM>-ym&Xp?T#K+GerWvz$xT*|ElOtHMoyTu`-lm4(j zI%qiw0Rb?28-~@h4+~h<>FFWNodxa+a41K4NAVz>MWH3#2S>9<_Z}if?%w^VcqqD%*`csx%2oDP8Y7mxA0cNJ=`_{`&Mj%*{a5eg3SFK6?_ zl*nPl4XfEFPd_B=7ToR(XBS>gPYy$o Of)ZXJQBz2~tqn0}%!@a-(M1pr$YUYc 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