From 8d4e87e5d87a35e7a8bee9cc53c5e51e0d3ee088 Mon Sep 17 00:00:00 2001 From: "maicon.senior" Date: Fri, 30 Jun 2017 13:50:16 -0300 Subject: [PATCH 1/2] Teste de Maicon Eduardo Prange para Serasa --- composer.json | 6 + composer.lock | 619 ++++++ composer.phar | Bin 0 -> 1838958 bytes src/JWTWrapper.php | 28 + vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 445 ++++ 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 | 18 + vendor/composer/autoload_real.php | 70 + vendor/composer/autoload_static.php | 92 + vendor/composer/installed.json | 623 ++++++ vendor/firebase/php-jwt/LICENSE | 30 + vendor/firebase/php-jwt/README.md | 200 ++ vendor/firebase/php-jwt/composer.json | 29 + .../php-jwt/src/BeforeValidException.php | 7 + .../firebase/php-jwt/src/ExpiredException.php | 7 + vendor/firebase/php-jwt/src/JWT.php | 379 ++++ .../php-jwt/src/SignatureInvalidException.php | 7 + vendor/pimple/pimple/.gitignore | 1 + vendor/pimple/pimple/.travis.yml | 6 + vendor/pimple/pimple/LICENSE | 19 + vendor/pimple/pimple/README.rst | 159 ++ vendor/pimple/pimple/composer.json | 25 + vendor/pimple/pimple/lib/Pimple.php | 214 ++ vendor/pimple/pimple/phpunit.xml.dist | 19 + .../pimple/tests/Pimple/Tests/Invokable.php | 38 + .../tests/Pimple/Tests/NonInvokable.php | 34 + .../pimple/tests/Pimple/Tests/PimpleTest.php | 331 +++ .../pimple/tests/Pimple/Tests/Service.php | 37 + vendor/pimple/pimple/tests/bootstrap.php | 15 + 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 | 37 + vendor/silex/silex/LICENSE | 19 + vendor/silex/silex/README.rst | 64 + vendor/silex/silex/bin/build | 67 + vendor/silex/silex/bin/compile | 9 + .../silex/bin/skeleton/fat_composer.json | 23 + vendor/silex/silex/bin/skeleton/index.php | 11 + .../silex/bin/skeleton/slim_composer.json | 5 + vendor/silex/silex/composer.json | 60 + vendor/silex/silex/doc/changelog.rst | 311 +++ vendor/silex/silex/doc/conf.py | 17 + vendor/silex/silex/doc/contributing.rst | 34 + vendor/silex/silex/doc/cookbook/assets.rst | 66 + .../silex/doc/cookbook/error_handler.rst | 38 + .../silex/silex/doc/cookbook/form_no_csrf.rst | 35 + 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 | 91 + .../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 | 53 + vendor/silex/silex/doc/middlewares.rst | 162 ++ .../silex/doc/organizing_controllers.rst | 73 + vendor/silex/silex/doc/providers.rst | 219 ++ vendor/silex/silex/doc/providers/doctrine.rst | 138 ++ vendor/silex/silex/doc/providers/form.rst | 194 ++ .../silex/silex/doc/providers/http_cache.rst | 128 ++ .../silex/doc/providers/http_fragment.rst | 74 + vendor/silex/silex/doc/providers/index.rst | 21 + vendor/silex/silex/doc/providers/monolog.rst | 105 + .../silex/silex/doc/providers/remember_me.rst | 69 + vendor/silex/silex/doc/providers/security.rst | 695 ++++++ .../silex/silex/doc/providers/serializer.rst | 73 + .../doc/providers/service_controller.rst | 116 + vendor/silex/silex/doc/providers/session.rst | 102 + .../silex/silex/doc/providers/swiftmailer.rst | 135 ++ .../silex/silex/doc/providers/translation.rst | 201 ++ vendor/silex/silex/doc/providers/twig.rst | 177 ++ .../silex/doc/providers/url_generator.rst | 79 + .../silex/silex/doc/providers/validator.rst | 218 ++ vendor/silex/silex/doc/services.rst | 256 +++ vendor/silex/silex/doc/testing.rst | 222 ++ vendor/silex/silex/doc/usage.rst | 782 +++++++ vendor/silex/silex/doc/web_servers.rst | 166 ++ vendor/silex/silex/phpunit.xml.dist | 24 + vendor/silex/silex/src/Silex/Application.php | 600 +++++ .../silex/src/Silex/Application/FormTrait.php | 41 + .../src/Silex/Application/MonologTrait.php | 36 + .../src/Silex/Application/SecurityTrait.php | 67 + .../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 | 70 + .../src/Silex/ConstraintValidatorFactory.php | 65 + vendor/silex/silex/src/Silex/Controller.php | 121 + .../silex/src/Silex/ControllerCollection.php | 221 ++ .../src/Silex/ControllerProviderInterface.php | 29 + .../silex/src/Silex/ControllerResolver.php | 52 + .../Silex/EventListener/ConverterListener.php | 66 + .../Silex/EventListener/LocaleListener.php | 47 + .../src/Silex/EventListener/LogListener.php | 128 ++ .../EventListener/MiddlewareListener.php | 96 + .../StringToResponseListener.php | 51 + .../Exception/ControllerFrozenException.php | 21 + .../silex/src/Silex/ExceptionHandler.php | 75 + .../src/Silex/ExceptionListenerWrapper.php | 93 + vendor/silex/silex/src/Silex/HttpCache.php | 39 + .../silex/silex/src/Silex/LazyUrlMatcher.php | 69 + .../Provider/DoctrineServiceProvider.php | 133 ++ .../Silex/Provider/FormServiceProvider.php | 121 + .../Provider/HttpCacheServiceProvider.php | 64 + .../Provider/HttpFragmentServiceProvider.php | 94 + .../Silex/Provider/MonologServiceProvider.php | 98 + .../Provider/RememberMeServiceProvider.php | 105 + .../Provider/SecurityServiceProvider.php | 597 +++++ .../Provider/SerializerServiceProvider.php | 63 + .../ServiceControllerServiceProvider.php | 31 + .../Silex/Provider/SessionServiceProvider.php | 124 ++ .../Provider/SwiftmailerServiceProvider.php | 99 + .../Provider/TranslationServiceProvider.php | 89 + .../Silex/Provider/TwigServiceProvider.php | 113 + .../Provider/UrlGeneratorServiceProvider.php | 37 + .../Provider/ValidatorServiceProvider.php | 70 + .../src/Silex/RedirectableUrlMatcher.php | 55 + vendor/silex/silex/src/Silex/Route.php | 188 ++ .../silex/src/Silex/Route/SecurityTrait.php | 31 + .../src/Silex/ServiceControllerResolver.php | 60 + .../src/Silex/ServiceProviderInterface.php | 37 + vendor/silex/silex/src/Silex/Translator.php | 48 + .../silex/silex/src/Silex/Util/Compiler.php | 188 ++ .../silex/src/Silex/ViewListenerWrapper.php | 87 + vendor/silex/silex/src/Silex/WebTestCase.php | 64 + .../Tests/Application/FormApplication.php | 19 + .../Silex/Tests/Application/FormTraitTest.php | 37 + .../Tests/Application/MonologApplication.php | 19 + .../Tests/Application/MonologTraitTest.php | 48 + .../Tests/Application/SecurityApplication.php | 19 + .../Tests/Application/SecurityTraitTest.php | 129 ++ .../Application/SwiftmailerApplication.php | 19 + .../Application/SwiftmailerTraitTest.php | 46 + .../Application/TranslationApplication.php | 19 + .../Application/TranslationTraitTest.php | 48 + .../Tests/Application/TwigApplication.php | 19 + .../Silex/Tests/Application/TwigTraitTest.php | 82 + .../Application/UrlGeneratorApplication.php | 19 + .../Application/UrlGeneratorTraitTest.php | 49 + .../tests/Silex/Tests/ApplicationTest.php | 699 ++++++ .../Silex/Tests/CallbackResolverTest.php | 52 + .../Silex/Tests/CallbackServicesTest.php | 109 + .../Silex/Tests/ControllerCollectionTest.php | 224 ++ .../Silex/Tests/ControllerResolverTest.php | 35 + .../tests/Silex/Tests/ControllerTest.php | 132 ++ .../Tests/EventListener/LogListenerTest.php | 94 + .../Silex/Tests/ExceptionHandlerTest.php | 406 ++++ .../tests/Silex/Tests/FunctionalTest.php | 58 + .../silex/tests/Silex/Tests/JsonTest.php | 56 + .../tests/Silex/Tests/LazyDispatcherTest.php | 59 + .../tests/Silex/Tests/LazyUrlMatcherTest.php | 109 + .../silex/tests/Silex/Tests/LocaleTest.php | 76 + .../tests/Silex/Tests/MiddlewareTest.php | 307 +++ .../Provider/DoctrineServiceProviderTest.php | 81 + .../Provider/FormServiceProviderTest.php | 235 ++ .../Provider/HttpCacheServiceProviderTest.php | 80 + .../HttpFragmentServiceProviderTest.php | 51 + .../Provider/MonologServiceProviderTest.php | 211 ++ .../RememberMeServiceProviderTest.php | 107 + .../Provider/SecurityServiceProviderTest.php | 292 +++ .../SerializerServiceProviderTest.php | 36 + .../Provider/SessionServiceProviderTest.php | 107 + .../tests/Silex/Tests/Provider/SpoolStub.php | 47 + .../SwiftmailerServiceProviderTest.php | 92 + .../TranslationServiceProviderTest.php | 150 ++ .../Provider/TwigServiceProviderTest.php | 85 + .../UrlGeneratorServiceProviderTest.php | 120 + .../Provider/ValidatorServiceProviderTest.php | 202 ++ .../Constraint/Custom.php | 29 + .../Constraint/CustomValidator.php | 32 + .../tests/Silex/Tests/Route/SecurityRoute.php | 19 + .../Silex/Tests/Route/SecurityTraitTest.php | 87 + .../silex/tests/Silex/Tests/RouterTest.php | 272 +++ .../ServiceControllerResolverRouterTest.php | 44 + .../Tests/ServiceControllerResolverTest.php | 89 + .../silex/tests/Silex/Tests/StreamTest.php | 52 + .../tests/Silex/Tests/WebTestCaseTest.php | 77 + 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 | 32 + .../ContainerAwareEventDispatcher.php | 195 ++ .../Debug/TraceableEventDispatcher.php | 372 ++++ .../TraceableEventDispatcherInterface.php | 34 + .../Debug/WrappedListener.php | 71 + .../RegisterListenersPass.php | 113 + vendor/symfony/event-dispatcher/Event.php | 58 + .../event-dispatcher/EventDispatcher.php | 188 ++ .../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 | 373 ++++ .../ContainerAwareEventDispatcherTest.php | 207 ++ .../Debug/TraceableEventDispatcherTest.php | 216 ++ .../RegisterListenersPassTest.php | 200 ++ .../Tests/EventDispatcherTest.php | 22 + .../event-dispatcher/Tests/EventTest.php | 54 + .../Tests/GenericEventTest.php | 139 ++ .../Tests/ImmutableEventDispatcherTest.php | 105 + vendor/symfony/event-dispatcher/composer.json | 44 + .../symfony/event-dispatcher/phpunit.xml.dist | 29 + 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 | 359 +++ vendor/symfony/http-foundation/CHANGELOG.md | 133 ++ vendor/symfony/http-foundation/Cookie.php | 190 ++ .../Exception/ConflictingHeadersException.php | 23 + .../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 | 808 +++++++ .../File/MimeType/MimeTypeGuesser.php | 144 ++ .../MimeType/MimeTypeGuesserInterface.php | 35 + .../http-foundation/File/UploadedFile.php | 293 +++ vendor/symfony/http-foundation/FileBag.php | 145 ++ vendor/symfony/http-foundation/HeaderBag.php | 324 +++ vendor/symfony/http-foundation/IpUtils.php | 133 ++ .../symfony/http-foundation/JsonResponse.php | 172 ++ vendor/symfony/http-foundation/LICENSE | 19 + .../symfony/http-foundation/ParameterBag.php | 238 ++ vendor/symfony/http-foundation/README.md | 14 + .../http-foundation/RedirectResponse.php | 99 + vendor/symfony/http-foundation/Request.php | 1951 ++++++++++++++++ .../http-foundation/RequestMatcher.php | 178 ++ .../RequestMatcherInterface.php | 29 + .../symfony/http-foundation/RequestStack.php | 103 + vendor/symfony/http-foundation/Response.php | 1181 ++++++++++ .../http-foundation/ResponseHeaderBag.php | 304 +++ 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 | 93 + .../http-foundation/Session/Session.php | 249 +++ .../Session/SessionBagInterface.php | 48 + .../Session/SessionInterface.php | 182 ++ .../Handler/MemcacheSessionHandler.php | 119 + .../Handler/MemcachedSessionHandler.php | 125 ++ .../Storage/Handler/MongoDbSessionHandler.php | 230 ++ .../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 | 398 ++++ .../Storage/PhpBridgeSessionStorage.php | 64 + .../Session/Storage/Proxy/AbstractProxy.php | 124 ++ .../Session/Storage/Proxy/NativeProxy.php | 41 + .../Storage/Proxy/SessionHandlerProxy.php | 87 + .../Storage/SessionStorageInterface.php | 139 ++ .../http-foundation/StreamedResponse.php | 114 + .../Tests/AcceptHeaderItemTest.php | 112 + .../Tests/AcceptHeaderTest.php | 102 + .../Tests/ApacheRequestTest.php | 92 + .../Tests/BinaryFileResponseTest.php | 330 +++ .../http-foundation/Tests/CookieTest.php | 154 ++ .../Tests/ExpressionRequestMatcherTest.php | 68 + .../http-foundation/Tests/File/FakeFile.php | 45 + .../http-foundation/Tests/File/FileTest.php | 179 ++ .../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 | 89 + .../Tests/File/UploadedFileTest.php | 272 +++ .../http-foundation/Tests/FileBagTest.php | 148 ++ .../http-foundation/Tests/HeaderBagTest.php | 195 ++ .../http-foundation/Tests/IpUtilsTest.php | 83 + .../Tests/JsonResponseTest.php | 225 ++ .../Tests/ParameterBagTest.php | 193 ++ .../Tests/RedirectResponseTest.php | 83 + .../Tests/RequestMatcherTest.php | 150 ++ .../Tests/RequestStackTest.php | 69 + .../http-foundation/Tests/RequestTest.php | 1979 +++++++++++++++++ .../Tests/ResponseHeaderBagTest.php | 297 +++ .../http-foundation/Tests/ResponseTest.php | 893 ++++++++ .../Tests/ResponseTestCase.php | 88 + .../http-foundation/Tests/ServerBagTest.php | 169 ++ .../Session/Attribute/AttributeBagTest.php | 188 ++ .../Attribute/NamespacedAttributeBagTest.php | 184 ++ .../Session/Flash/AutoExpireFlashBagTest.php | 155 ++ .../Tests/Session/Flash/FlashBagTest.php | 134 ++ .../Tests/Session/SessionTest.php | 221 ++ .../Handler/MemcacheSessionHandlerTest.php | 138 ++ .../Handler/MemcachedSessionHandlerTest.php | 138 ++ .../Handler/MongoDbSessionHandlerTest.php | 327 +++ .../Handler/NativeFileSessionHandlerTest.php | 76 + .../Handler/NativeSessionHandlerTest.php | 33 + .../Handler/NullSessionHandlerTest.php | 58 + .../Storage/Handler/PdoSessionHandlerTest.php | 369 +++ .../Handler/WriteCheckSessionHandlerTest.php | 94 + .../Tests/Session/Storage/MetadataBagTest.php | 138 ++ .../Storage/MockArraySessionStorageTest.php | 106 + .../Storage/MockFileSessionStorageTest.php | 126 ++ .../Storage/NativeSessionStorageTest.php | 246 ++ .../Storage/PhpBridgeSessionStorageTest.php | 95 + .../Storage/Proxy/AbstractProxyTest.php | 144 ++ .../Session/Storage/Proxy/NativeProxyTest.php | 35 + .../Storage/Proxy/SessionHandlerProxyTest.php | 123 + .../Tests/StreamedResponseTest.php | 112 + vendor/symfony/http-foundation/composer.json | 37 + .../symfony/http-foundation/phpunit.xml.dist | 29 + vendor/symfony/http-kernel/.gitignore | 5 + vendor/symfony/http-kernel/Bundle/Bundle.php | 221 ++ .../http-kernel/Bundle/BundleInterface.php | 84 + vendor/symfony/http-kernel/CHANGELOG.md | 104 + .../CacheClearer/CacheClearerInterface.php | 27 + .../CacheClearer/ChainCacheClearer.php | 55 + .../http-kernel/CacheWarmer/CacheWarmer.php | 32 + .../CacheWarmer/CacheWarmerAggregate.php | 74 + .../CacheWarmer/CacheWarmerInterface.php | 32 + .../CacheWarmer/WarmableInterface.php | 27 + vendor/symfony/http-kernel/Client.php | 226 ++ .../Config/EnvParametersResource.php | 95 + .../http-kernel/Config/FileLocator.php | 56 + .../Controller/ControllerReference.php | 46 + .../Controller/ControllerResolver.php | 229 ++ .../ControllerResolverInterface.php | 57 + .../TraceableControllerResolver.php | 66 + .../DataCollector/AjaxDataCollector.php | 33 + .../DataCollector/ConfigDataCollector.php | 291 +++ .../DataCollector/DataCollector.php | 58 + .../DataCollector/DataCollectorInterface.php | 39 + .../DataCollector/DumpDataCollector.php | 295 +++ .../DataCollector/EventDataCollector.php | 107 + .../DataCollector/ExceptionDataCollector.php | 104 + .../LateDataCollectorInterface.php | 25 + .../DataCollector/LoggerDataCollector.php | 216 ++ .../DataCollector/MemoryDataCollector.php | 109 + .../DataCollector/RequestDataCollector.php | 342 +++ .../DataCollector/RouterDataCollector.php | 102 + .../DataCollector/TimeDataCollector.php | 136 ++ .../DataCollector/Util/ValueExporter.php | 89 + .../Debug/TraceableEventDispatcher.php | 82 + .../AddClassesToCachePass.php | 46 + .../ConfigurableExtension.php | 45 + .../DependencyInjection/Extension.php | 44 + .../FragmentRendererPass.php | 70 + .../LazyLoadingFragmentHandler.php | 65 + .../MergeExtensionConfigurationPass.php | 41 + .../Event/FilterControllerEvent.php | 63 + .../http-kernel/Event/FilterResponseEvent.php | 62 + .../http-kernel/Event/FinishRequestEvent.php | 21 + .../http-kernel/Event/GetResponseEvent.php | 65 + .../GetResponseForControllerResultEvent.php | 61 + .../Event/GetResponseForExceptionEvent.php | 67 + .../symfony/http-kernel/Event/KernelEvent.php | 94 + .../http-kernel/Event/PostResponseEvent.php | 46 + .../AddRequestFormatsListener.php | 57 + .../EventListener/DebugHandlersListener.php | 138 ++ .../EventListener/DumpListener.php | 55 + .../EventListener/ExceptionListener.php | 116 + .../EventListener/FragmentListener.php | 103 + .../EventListener/LocaleListener.php | 85 + .../EventListener/ProfilerListener.php | 135 ++ .../EventListener/ResponseListener.php | 58 + .../EventListener/RouterListener.php | 138 ++ .../EventListener/SaveSessionListener.php | 66 + .../EventListener/SessionListener.php | 53 + .../StreamedResponseListener.php | 51 + .../EventListener/SurrogateListener.php | 58 + .../EventListener/TestSessionListener.php | 83 + .../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 | 41 + .../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 | 95 + .../Fragment/EsiFragmentRenderer.php | 28 + .../http-kernel/Fragment/FragmentHandler.php | 118 + .../Fragment/FragmentRendererInterface.php | 42 + .../Fragment/HIncludeFragmentRenderer.php | 160 ++ .../Fragment/InlineFragmentRenderer.php | 154 ++ .../Fragment/RoutableFragmentRenderer.php | 90 + .../Fragment/SsiFragmentRenderer.php | 28 + vendor/symfony/http-kernel/HttpCache/Esi.php | 249 +++ .../http-kernel/HttpCache/HttpCache.php | 689 ++++++ .../HttpCache/ResponseCacheStrategy.php | 93 + .../ResponseCacheStrategyInterface.php | 41 + vendor/symfony/http-kernel/HttpCache/Ssi.php | 194 ++ .../symfony/http-kernel/HttpCache/Store.php | 489 ++++ .../http-kernel/HttpCache/StoreInterface.php | 96 + .../HttpCache/SurrogateInterface.php | 103 + vendor/symfony/http-kernel/HttpKernel.php | 290 +++ .../http-kernel/HttpKernelInterface.php | 43 + vendor/symfony/http-kernel/Kernel.php | 733 ++++++ 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 | 292 +++ .../symfony/http-kernel/Profiler/Profiler.php | 269 +++ .../Profiler/ProfilerStorageInterface.php | 59 + vendor/symfony/http-kernel/README.md | 16 + .../http-kernel/TerminableInterface.php | 35 + .../http-kernel/Tests/Bundle/BundleTest.php | 54 + .../CacheClearer/ChainCacheClearerTest.php | 57 + .../CacheWarmer/CacheWarmerAggregateTest.php | 100 + .../Tests/CacheWarmer/CacheWarmerTest.php | 67 + .../symfony/http-kernel/Tests/ClientTest.php | 179 ++ .../Config/EnvParametersResourceTest.php | 106 + .../Tests/Config/FileLocatorTest.php | 47 + .../Controller/ControllerResolverTest.php | 276 +++ .../DataCollector/ConfigDataCollectorTest.php | 81 + .../DataCollector/DumpDataCollectorTest.php | 119 + .../ExceptionDataCollectorTest.php | 39 + .../DataCollector/LoggerDataCollectorTest.php | 95 + .../DataCollector/MemoryDataCollectorTest.php | 58 + .../RequestDataCollectorTest.php | 222 ++ .../DataCollector/TimeDataCollectorTest.php | 54 + .../DataCollector/Util/ValueExporterTest.php | 47 + .../Debug/TraceableEventDispatcherTest.php | 117 + .../FragmentRendererPassTest.php | 111 + .../LazyLoadingFragmentHandlerTest.php | 40 + .../MergeExtensionConfigurationPassTest.php | 64 + .../AddRequestFormatsListenerTest.php | 83 + .../DebugHandlersListenerTest.php | 134 ++ .../Tests/EventListener/DumpListenerTest.php | 82 + .../EventListener/ExceptionListenerTest.php | 148 ++ .../EventListener/FragmentListenerTest.php | 121 + .../EventListener/LocaleListenerTest.php | 102 + .../EventListener/ProfilerListenerTest.php | 70 + .../EventListener/ResponseListenerTest.php | 94 + .../EventListener/RouterListenerTest.php | 157 ++ .../EventListener/SurrogateListenerTest.php | 66 + .../EventListener/TestSessionListenerTest.php | 132 ++ .../EventListener/TranslatorListenerTest.php | 117 + .../ValidateRequestListenerTest.php | 42 + .../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/VariadicController.php | 10 + .../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 + .../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 | 97 + .../Tests/Fragment/FragmentHandlerTest.php | 98 + .../Fragment/HIncludeFragmentRendererTest.php | 88 + .../Fragment/InlineFragmentRendererTest.php | 223 ++ .../Fragment/RoutableFragmentRendererTest.php | 93 + .../http-kernel/Tests/HttpCache/EsiTest.php | 247 ++ .../Tests/HttpCache/HttpCacheTest.php | 1263 +++++++++++ .../Tests/HttpCache/HttpCacheTestCase.php | 180 ++ .../HttpCache/ResponseCacheStrategyTest.php | 77 + .../http-kernel/Tests/HttpCache/SsiTest.php | 214 ++ .../http-kernel/Tests/HttpCache/StoreTest.php | 273 +++ .../Tests/HttpCache/TestHttpKernel.php | 91 + .../HttpCache/TestMultipleHttpKernel.php | 80 + .../http-kernel/Tests/HttpKernelTest.php | 329 +++ .../symfony/http-kernel/Tests/KernelTest.php | 816 +++++++ vendor/symfony/http-kernel/Tests/Logger.php | 88 + .../Profiler/FileProfilerStorageTest.php | 335 +++ .../Tests/Profiler/ProfilerTest.php | 82 + .../http-kernel/Tests/TestHttpKernel.php | 41 + .../http-kernel/Tests/UriSignerTest.php | 52 + vendor/symfony/http-kernel/UriSigner.php | 109 + vendor/symfony/http-kernel/composer.json | 66 + vendor/symfony/http-kernel/phpunit.xml.dist | 38 + 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 | 200 ++ vendor/symfony/routing/CompiledRoute.php | 166 ++ .../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 | 326 +++ .../Generator/UrlGeneratorInterface.php | 84 + vendor/symfony/routing/LICENSE | 19 + .../routing/Loader/AnnotationClassLoader.php | 265 +++ .../Loader/AnnotationDirectoryLoader.php | 81 + .../routing/Loader/AnnotationFileLoader.php | 142 ++ .../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 | 241 ++ .../symfony/routing/Loader/YamlFileLoader.php | 207 ++ .../Loader/schema/routing/routing-1.0.xsd | 64 + .../Matcher/Dumper/DumperCollection.php | 161 ++ .../Matcher/Dumper/DumperPrefixCollection.php | 107 + .../routing/Matcher/Dumper/DumperRoute.php | 66 + .../routing/Matcher/Dumper/MatcherDumper.php | 45 + .../Matcher/Dumper/MatcherDumperInterface.php | 39 + .../Matcher/Dumper/PhpMatcherDumper.php | 409 ++++ .../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 | 588 +++++ vendor/symfony/routing/RouteCollection.php | 277 +++ .../routing/RouteCollectionBuilder.php | 372 ++++ vendor/symfony/routing/RouteCompiler.php | 229 ++ .../routing/RouteCompilerInterface.php | 32 + vendor/symfony/routing/Router.php | 378 ++++ vendor/symfony/routing/RouterInterface.php | 32 + .../routing/Tests/Annotation/RouteTest.php | 49 + .../routing/Tests/CompiledRouteTest.php | 26 + .../AnnotatedClasses/AbstractClass.php | 16 + .../Fixtures/AnnotatedClasses/BarClass.php | 19 + .../Fixtures/AnnotatedClasses/FooClass.php | 16 + .../Fixtures/AnnotatedClasses/FooTrait.php | 13 + .../Tests/Fixtures/CustomXmlFileLoader.php | 26 + .../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 | 312 +++ .../Tests/Fixtures/dumper/url_matcher2.apache | 7 + .../Tests/Fixtures/dumper/url_matcher2.php | 344 +++ .../Tests/Fixtures/dumper/url_matcher3.php | 50 + .../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/missing_id.xml | 8 + .../routing/Tests/Fixtures/missing_path.xml | 8 + .../Tests/Fixtures/namespaceprefix.xml | 13 + .../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/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 | 180 ++ .../Tests/Generator/UrlGeneratorTest.php | 655 ++++++ .../Loader/AbstractAnnotationLoaderTest.php | 31 + .../Loader/AnnotationClassLoaderTest.php | 188 ++ .../Loader/AnnotationDirectoryLoaderTest.php | 53 + .../Tests/Loader/AnnotationFileLoaderTest.php | 71 + .../Tests/Loader/ClosureLoaderTest.php | 48 + .../Tests/Loader/DirectoryLoaderTest.php | 74 + .../Tests/Loader/ObjectRouteLoaderTest.php | 122 + .../Tests/Loader/PhpFileLoaderTest.php | 82 + .../Tests/Loader/XmlFileLoaderTest.php | 132 ++ .../Tests/Loader/YamlFileLoaderTest.php | 110 + .../Matcher/Dumper/DumperCollectionTest.php | 33 + .../Dumper/DumperPrefixCollectionTest.php | 123 + .../Matcher/Dumper/PhpMatcherDumperTest.php | 287 +++ .../Matcher/RedirectableUrlMatcherTest.php | 71 + .../Tests/Matcher/TraceableUrlMatcherTest.php | 121 + .../routing/Tests/Matcher/UrlMatcherTest.php | 419 ++++ .../routing/Tests/RequestContextTest.php | 159 ++ .../Tests/RouteCollectionBuilderTest.php | 324 +++ .../routing/Tests/RouteCollectionTest.php | 304 +++ .../routing/Tests/RouteCompilerTest.php | 267 +++ vendor/symfony/routing/Tests/RouteTest.php | 239 ++ vendor/symfony/routing/Tests/RouterTest.php | 161 ++ vendor/symfony/routing/composer.json | 53 + vendor/symfony/routing/phpunit.xml.dist | 28 + web/.htaccess | 11 + web/index.php | 120 + 715 files changed, 85311 insertions(+) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 composer.phar create mode 100644 src/JWTWrapper.php 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/firebase/php-jwt/LICENSE create mode 100644 vendor/firebase/php-jwt/README.md create mode 100644 vendor/firebase/php-jwt/composer.json create mode 100644 vendor/firebase/php-jwt/src/BeforeValidException.php create mode 100644 vendor/firebase/php-jwt/src/ExpiredException.php create mode 100644 vendor/firebase/php-jwt/src/JWT.php create mode 100644 vendor/firebase/php-jwt/src/SignatureInvalidException.php create mode 100644 vendor/pimple/pimple/.gitignore create mode 100644 vendor/pimple/pimple/.travis.yml 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/lib/Pimple.php create mode 100644 vendor/pimple/pimple/phpunit.xml.dist create mode 100644 vendor/pimple/pimple/tests/Pimple/Tests/Invokable.php create mode 100644 vendor/pimple/pimple/tests/Pimple/Tests/NonInvokable.php create mode 100644 vendor/pimple/pimple/tests/Pimple/Tests/PimpleTest.php create mode 100644 vendor/pimple/pimple/tests/Pimple/Tests/Service.php create mode 100644 vendor/pimple/pimple/tests/bootstrap.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/bin/build create mode 100644 vendor/silex/silex/bin/compile create mode 100644 vendor/silex/silex/bin/skeleton/fat_composer.json create mode 100644 vendor/silex/silex/bin/skeleton/index.php create mode 100644 vendor/silex/silex/bin/skeleton/slim_composer.json 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/assets.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/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/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/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/url_generator.rst create mode 100644 vendor/silex/silex/doc/providers/validator.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/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/ConstraintValidatorFactory.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/ControllerProviderInterface.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/LocaleListener.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/HttpCache.php create mode 100644 vendor/silex/silex/src/Silex/LazyUrlMatcher.php create mode 100644 vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/FormServiceProvider.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/MonologServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.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/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/TwigServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/UrlGeneratorServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.php create mode 100644 vendor/silex/silex/src/Silex/RedirectableUrlMatcher.php 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/ServiceProviderInterface.php create mode 100644 vendor/silex/silex/src/Silex/Translator.php create mode 100644 vendor/silex/silex/src/Silex/Util/Compiler.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/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/LazyUrlMatcherTest.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/DoctrineServiceProviderTest.php create mode 100644 vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.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/SecurityServiceProviderTest.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/UrlGeneratorServiceProviderTest.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/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/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/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/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/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/TraceableControllerResolver.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/TraceableEventDispatcher.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/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/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/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/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/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/ControllerResolverTest.php create mode 100644 vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.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/TraceableEventDispatcherTest.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/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/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/VariadicController.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/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/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/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/DumperPrefixCollection.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/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/Fixtures/AnnotatedClasses/AbstractClass.php create mode 100644 vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/BarClass.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/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/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/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/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/DumperPrefixCollectionTest.php create mode 100644 vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.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..325f6547 --- /dev/null +++ b/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "silex/silex": "~1.3", + "firebase/php-jwt": "^5.0" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..fa789e76 --- /dev/null +++ b/composer.lock @@ -0,0 +1,619 @@ +{ + "_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" + ], + "content-hash": "34355f1d6b877be3c7ca80686cf29041", + "packages": [ + { + "name": "firebase/php-jwt", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": " 4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "time": "2017-06-27T22:17:23+00:00" + }, + { + "name": "pimple/pimple", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d", + "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2013-11-22T08:30:29+00:00" + }, + { + "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-10T12:19:37+00:00" + }, + { + "name": "silex/silex", + "version": "v1.3.6", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex.git", + "reference": "ff8aa6bc2e066e14b07e0c63e9bd9dd1458af136" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex/zipball/ff8aa6bc2e066e14b07e0c63e9bd9dd1458af136", + "reference": "ff8aa6bc2e066e14b07e0c63e9bd9dd1458af136", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "pimple/pimple": "~1.0", + "symfony/event-dispatcher": "~2.3|3.0.*", + "symfony/http-foundation": "~2.3|3.0.*", + "symfony/http-kernel": "~2.3|3.0.*", + "symfony/routing": "~2.3|3.0.*" + }, + "require-dev": { + "doctrine/dbal": "~2.2", + "monolog/monolog": "^1.4.1", + "swiftmailer/swiftmailer": "~5", + "symfony/browser-kit": "~2.3|3.0.*", + "symfony/config": "~2.3|3.0.*", + "symfony/css-selector": "~2.3|3.0.*", + "symfony/debug": "~2.3|3.0.*", + "symfony/dom-crawler": "~2.3|3.0.*", + "symfony/finder": "~2.3|3.0.*", + "symfony/form": "~2.3|3.0.*", + "symfony/intl": "~2.3|3.0.*", + "symfony/monolog-bridge": "~2.3|3.0.*", + "symfony/options-resolver": "~2.3|3.0.*", + "symfony/phpunit-bridge": "~2.7", + "symfony/process": "~2.3|3.0.*", + "symfony/security": "~2.3|3.0.*", + "symfony/serializer": "~2.3|3.0.*", + "symfony/translation": "~2.3|3.0.*", + "symfony/twig-bridge": "~2.3|3.0.*", + "symfony/validator": "~2.3|3.0.*", + "twig/twig": "~1.28|~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.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-04-30T16:26:54+00:00" + }, + { + "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-01T21:01:25+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "54da3ff63dec3c9c0e32ec3f95a7d94ef64baa00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/54da3ff63dec3c9c0e32ec3f95a7d94ef64baa00", + "reference": "54da3ff63dec3c9c0e32ec3f95a7d94ef64baa00", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "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.0-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": "2016-07-19T10:44:15+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "49ba00f8ede742169cb6b70abe33243f4d673f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49ba00f8ede742169cb6b70abe33243f4d673f82", + "reference": "49ba00f8ede742169cb6b70abe33243f4d673f82", + "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.0-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": "2016-07-17T13:54:30+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v3.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "d97ba4425e36e79c794e7d14ff36f00f081b37b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d97ba4425e36e79c794e7d14ff36f00f081b37b3", + "reference": "d97ba4425e36e79c794e7d14ff36f00f081b37b3", + "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": "~2.8.8|~3.0.8|~3.1.2|~3.2" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "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": "~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/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": "~2.8|~3.0" + }, + "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.0-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": "2016-07-30T09:10:37+00:00" + }, + { + "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-09T14:24:12+00:00" + }, + { + "name": "symfony/routing", + "version": "v3.0.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "9038984bd9c05ab07280121e9e10f61a7231457b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/9038984bd9c05ab07280121e9e10f61a7231457b", + "reference": "9038984bd9c05ab07280121e9e10f61a7231457b", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "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.0-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": "2016-06-29T05:40:00+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/composer.phar b/composer.phar new file mode 100644 index 0000000000000000000000000000000000000000..e44722e933f8ebf9b4501f04b42c9accc594ab00 GIT binary patch literal 1838958 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Π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%0Da~=Lksv!yU|KE+@g9Sd8E3p>Uj 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

'; + } + + $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); + } 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:{}'; + } + + $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); + $this->dataCount = count($this->data); + self::__construct($this->stopwatch); + } + + 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); + } 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)); + + rewind($data); + $dump['data'] = stream_get_contents($data); + 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); + } 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, $fileLinkFormat) { + if ($this instanceof HtmlDumper) { + if ('' !== $file) { + $s = $this->style('meta', '%s'); + $name = strip_tags($this->style('', $name)); + $file = strip_tags($this->style('', $file)); + if ($fileLinkFormat) { + $link = strtr(strip_tags($this->style('', $fileLinkFormat)), array('%f' => $file, '%l' => (int) $line)); + $name = sprintf(''.$s.'', $link, $file, $name); + } else { + $name = sprintf(''.$s.'', $file, $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..0a87bc38 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.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\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()); + } + } + + /** + * 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..11a4cc81 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php @@ -0,0 +1,216 @@ + + * + * 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\Log\DebugLoggerInterface; + +/** + * LogDataCollector. + * + * @author Fabien Potencier + */ +class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private $errorNames = array( + E_DEPRECATED => 'E_DEPRECATED', + E_USER_DEPRECATED => 'E_USER_DEPRECATED', + E_NOTICE => 'E_NOTICE', + E_USER_NOTICE => 'E_USER_NOTICE', + E_STRICT => 'E_STRICT', + E_WARNING => 'E_WARNING', + E_USER_WARNING => 'E_USER_WARNING', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_CORE_WARNING => 'E_CORE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_PARSE => 'E_PARSE', + E_ERROR => 'E_ERROR', + E_CORE_ERROR => 'E_CORE_ERROR', + ); + + private $logger; + + public function __construct($logger = null) + { + if (null !== $logger && $logger instanceof DebugLoggerInterface) { + $this->logger = $logger; + } + } + + /** + * {@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(); + $this->data['logs'] = $this->sanitizeLogs($this->logger->getLogs()); + } + } + + /** + * Gets the called events. + * + * @return array An array of called events + * + * @see TraceableEventDispatcherInterface + */ + public function countErrors() + { + return isset($this->data['error_count']) ? $this->data['error_count'] : 0; + } + + /** + * 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 countDeprecations() + { + return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0; + } + + public function countScreams() + { + return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'logger'; + } + + private function sanitizeLogs($logs) + { + $errorContextById = array(); + $sanitizedLogs = array(); + + foreach ($logs as $log) { + $context = $this->sanitizeContext($log['context']); + + if (isset($context['type'], $context['file'], $context['line'], $context['level'])) { + $errorId = md5("{$context['type']}/{$context['line']}/{$context['file']}\x00{$log['message']}", true); + $silenced = !($context['type'] & $context['level']); + if (isset($this->errorNames[$context['type']])) { + $context = array_merge(array('name' => $this->errorNames[$context['type']]), $context); + } + + if (isset($errorContextById[$errorId])) { + if (isset($errorContextById[$errorId]['errorCount'])) { + ++$errorContextById[$errorId]['errorCount']; + } else { + $errorContextById[$errorId]['errorCount'] = 2; + } + + if (!$silenced && isset($errorContextById[$errorId]['scream'])) { + unset($errorContextById[$errorId]['scream']); + $errorContextById[$errorId]['level'] = $context['level']; + } + + continue; + } + + $errorContextById[$errorId] = &$context; + if ($silenced) { + $context['scream'] = true; + } + + $log['context'] = &$context; + unset($context); + } else { + $log['context'] = $context; + } + + $sanitizedLogs[] = $log; + } + + return $sanitizedLogs; + } + + private function sanitizeContext($context) + { + if (is_array($context)) { + foreach ($context as $key => $value) { + $context[$key] = $this->sanitizeContext($value); + } + + return $context; + } + + if (is_resource($context)) { + return sprintf('Resource(%s)', get_resource_type($context)); + } + + if (is_object($context)) { + return sprintf('Object(%s)', get_class($context)); + } + + return $context; + } + + private function computeErrorsCount() + { + $count = array( + 'error_count' => $this->logger->countErrors(), + 'deprecation_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 (isset($log['context']['type'], $log['context']['level'])) { + if (E_DEPRECATED === $log['context']['type'] || E_USER_DEPRECATED === $log['context']['type']) { + ++$count['deprecation_count']; + } elseif (!($log['context']['type'] & $log['context']['level'])) { + ++$count['scream_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..9a499a73 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.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\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\HeaderBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +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 +{ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $responseHeaders = $response->headers->all(); + $cookies = array(); + foreach ($response->headers->getCookies() as $cookie) { + $cookies[] = $this->getCookieHeader($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + if (count($cookies) > 0) { + $responseHeaders['Set-Cookie'] = $cookies; + } + + // attributes are serialized and as they can be anything, they need to be converted to strings. + $attributes = array(); + foreach ($request->attributes->all() as $key => $value) { + if ('_route' === $key && is_object($value)) { + $attributes[$key] = $this->varToString($value->getPath()); + } elseif ('_route_params' === $key) { + // we need to keep route params as an array (see getRouteParams()) + foreach ($value as $k => $v) { + $value[$k] = $this->varToString($v); + } + $attributes[$key] = $value; + } else { + $attributes[$key] = $this->varToString($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(); + $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(); + + $this->data = array( + '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, + 'response_headers' => $responseHeaders, + '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'] = '******'; + } + + if (isset($this->controllers[$request])) { + $controller = $this->controllers[$request]; + if (is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + $this->data['controller'] = 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 + $this->data['controller'] = array( + 'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => 'n/a', + 'line' => 'n/a', + ); + } + } + } elseif ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + $this->data['controller'] = array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } elseif (is_object($controller)) { + $r = new \ReflectionClass($controller); + $this->data['controller'] = array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } else { + $this->data['controller'] = (string) $controller ?: 'n/a'; + } + unset($this->controllers[$request]); + } + } + + public function getPathInfo() + { + return $this->data['path_info']; + } + + public function getRequestRequest() + { + return new ParameterBag($this->data['request_request']); + } + + public function getRequestQuery() + { + return new ParameterBag($this->data['request_query']); + } + + public function getRequestHeaders() + { + return new HeaderBag($this->data['request_headers']); + } + + public function getRequestServer() + { + return new ParameterBag($this->data['request_server']); + } + + public function getRequestCookies() + { + return new ParameterBag($this->data['request_cookies']); + } + + public function getRequestAttributes() + { + return new ParameterBag($this->data['request_attributes']); + } + + public function getResponseHeaders() + { + return new ResponseHeaderBag($this->data['response_headers']); + } + + public function getSessionMetadata() + { + return $this->data['session_metadata']; + } + + public function getSessionAttributes() + { + return $this->data['session_attributes']; + } + + public function getFlashes() + { + return $this->data['flashes']; + } + + 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 isset($this->data['request_attributes']['_route']) ? $this->data['request_attributes']['_route'] : ''; + } + + /** + * 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'] : array(); + } + + /** + * Gets the controller. + * + * @return string The controller as a string + */ + public function getController() + { + return $this->data['controller']; + } + + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + public static function getSubscribedEvents() + { + return array(KernelEvents::CONTROLLER => 'onKernelController'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'request'; + } + + private function getCookieHeader($name, $value, $expires, $path, $domain, $secure, $httponly) + { + $cookie = sprintf('%s=%s', $name, urlencode($value)); + + if (0 !== $expires) { + if (is_numeric($expires)) { + $expires = (int) $expires; + } elseif ($expires instanceof \DateTime) { + $expires = $expires->getTimestamp(); + } else { + $tmp = strtotime($expires); + if (false === $tmp || -1 == $tmp) { + throw new \InvalidArgumentException(sprintf('The "expires" cookie parameter is not valid (%s).', $expires)); + } + $expires = $tmp; + } + + $cookie .= '; expires='.str_replace('+0000', '', \DateTime::createFromFormat('U', $expires, new \DateTimeZone('GMT'))->format('D, d-M-Y H:i:s T')); + } + + if ($domain) { + $cookie .= '; domain='.$domain; + } + + $cookie .= '; path='.$path; + + if ($secure) { + $cookie .= '; secure'; + } + + if ($httponly) { + $cookie .= '; httponly'; + } + + return $cookie; + } +} 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..4ccaafa3 --- /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', $request->server->get('REQUEST_TIME')); + } + + $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..b06dd83b --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.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\DataCollector\Util; + +/** + * @author Bernhard Schussek + */ +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 (is_object($value)) { + if ($value instanceof \DateTimeInterface) { + return sprintf('Object(%s) - %s', get_class($value), $value->format(\DateTime::ISO8601)); + } + + return sprintf('Object(%s)', get_class($value)); + } + + if ($value instanceof \__PHP_Incomplete_Class) { + return sprintf('__PHP_Incomplete_Class(%s)', $this->getClassNameFromIncomplete($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)); + } + + return sprintf('[%s]', implode(', ', $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/TraceableEventDispatcher.php b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php new file mode 100644 index 00000000..9644b049 --- /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: + $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/AddClassesToCachePass.php b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php new file mode 100644 index 00000000..09af6bd2 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.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\DependencyInjection; + +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 AddClassesToCachePass implements CompilerPassInterface +{ + private $kernel; + + public function __construct(Kernel $kernel) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $classes = array(); + foreach ($container->getExtensions() as $extension) { + if ($extension instanceof Extension) { + $classes = array_merge($classes, $extension->getClassesToCompile()); + } + } + + $this->kernel->setClassCache(array_unique($container->getParameterBag()->resolveValue($classes))); + } +} 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/Extension.php b/vendor/symfony/http-kernel/DependencyInjection/Extension.php new file mode 100644 index 00000000..2ca0f132 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/Extension.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\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(); + + /** + * Gets the classes to cache. + * + * @return array An array of classes + */ + public function getClassesToCompile() + { + return $this->classes; + } + + /** + * Adds classes to the class cache. + * + * @param array $classes An array of classes + */ + public function addClassesToCompile(array $classes) + { + $this->classes = array_merge($this->classes, $classes); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php new file mode 100644 index 00000000..1a41924f --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.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\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * 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); + foreach ($container->findTaggedServiceIds($this->rendererTag) as $id => $tags) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as fragment renderer are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as fragment renderer are lazy-loaded.', $id)); + } + + $class = $container->getParameterBag()->resolveValue($def->getClass()); + $interface = 'Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface'; + + 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)); + } + + foreach ($tags as $tag) { + $definition->addMethodCall('addRendererService', array($tag['alias'], $id)); + } + } + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php new file mode 100644 index 00000000..78995623 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.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\DependencyInjection; + +use Symfony\Component\DependencyInjection\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; + private $rendererIds = 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 $renderer The render service id + */ + public function addRendererService($name, $renderer) + { + $this->rendererIds[$name] = $renderer; + } + + /** + * {@inheritdoc} + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + if (isset($this->rendererIds[$renderer])) { + $this->addRenderer($this->container->get($this->rendererIds[$renderer])); + + unset($this->rendererIds[$renderer]); + } + + 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/Event/FilterControllerEvent.php b/vendor/symfony/http-kernel/Event/FilterControllerEvent.php new file mode 100644 index 00000000..e0af1310 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterControllerEvent.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\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 + * + * @throws \LogicException + */ + 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..003953fe --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.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\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; + + 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; + } +} 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/AddRequestFormatsListener.php b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php new file mode 100644 index 00000000..14a5d435 --- /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 => 'onKernelRequest'); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php new file mode 100644 index 00000000..b0697ab6 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.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\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 $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 $fileLinkFormat The format for links to source files + */ + public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, $throwAt = E_ALL, $scream = true, $fileLinkFormat = null) + { + $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 ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + } + + /** + * 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)) { + $scream = 0; + foreach ($this->levels as $type => $log) { + $scream |= $type; + } + } else { + $scream = $this->levels; + } + if ($this->scream) { + $handler->screamAt($scream); + } + $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 (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..2f302f45 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DumpListener.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\KernelEvents; +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() + { + // Register early to have a working dump() as early as possible + return array(KernelEvents::REQUEST => 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..9fdaccfa --- /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()) { + 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..01a51b13 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/RouterListener.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\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(sprintf('Matched route "%s".', isset($parameters['_route']) ? $parameters['_route'] : 'n/a'), array( + 'route_parameters' => $parameters, + 'request_uri' => $request->getUri(), + )); + } + + $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..ecf065f0 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SessionListener.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +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 SessionListener 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/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..8fc8e575 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/TestSessionListener.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\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\Cookie; +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 TestSessionListener 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/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..00096ccf --- /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 that the headers and other information indicating the + * client IP address of a request are consistent. + * + * @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()) { + // This will throw an exception if the headers are inconsistent. + $request->getClientIps(); + } + } + + /** + * {@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..4e1b5263 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpException.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\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; + } +} 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..1968001a --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.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\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)) { + 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())); + } +} 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..304b5278 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.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\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; + +/** + * Implements the Hinclude rendering strategy. + * + * @author Fabien Potencier + */ +class HIncludeFragmentRenderer extends RoutableFragmentRenderer +{ + private $globalDefaultTemplate; + private $signer; + private $templating; + private $charset; + + /** + * Constructor. + * + * @param EngineInterface|\Twig_Environment $templating An EngineInterface or a \Twig_Environment 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|\Twig_Environment|null $templating An EngineInterface or a \Twig_Environment instance + * + * @throws \InvalidArgumentException + */ + public function setTemplating($templating) + { + if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof \Twig_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 \Twig_ExistsLoaderInterface) { + return $loader->exists($template); + } + + try { + $loader->getSource($template); + + return true; + } catch (\Twig_Error_Loader $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..a61b239c --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php @@ -0,0 +1,154 @@ + + * + * 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 ($trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { + $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/Esi.php b/vendor/symfony/http-kernel/HttpCache/Esi.php new file mode 100644 index 00000000..5b231d29 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Esi.php @@ -0,0 +1,249 @@ + + * + * 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; + +/** + * 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 implements SurrogateInterface +{ + private $contentTypes; + private $phpEscapeMap = array( + array('', '', '', ''), + ); + + /** + * Constructor. + * + * @param array $contentTypes An array of content-type that should be parsed for ESI 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; + } + + public function getName() + { + return 'esi'; + } + + /** + * Returns a new cache strategy instance. + * + * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + */ + public function createCacheStrategy() + { + return new ResponseCacheStrategy(); + } + + /** + * Checks that at least one surrogate has ESI/1.0 capability. + * + * @param Request $request A Request instance + * + * @return bool true if one surrogate has ESI/1.0 capability, false otherwise + */ + public function hasSurrogateCapability(Request $request) + { + if (null === $value = $request->headers->get('Surrogate-Capability')) { + return false; + } + + return false !== strpos($value, 'ESI/1.0'); + } + + /** + * Adds ESI/1.0 capability to the given Request. + * + * @param Request $request A Request instance + */ + public function addSurrogateCapability(Request $request) + { + $current = $request->headers->get('Surrogate-Capability'); + $new = 'symfony2="ESI/1.0"'; + + $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); + } + + /** + * Adds HTTP headers to specify that the Response needs to be parsed for ESI. + * + * This method only adds an ESI HTTP header if the Response has some ESI tags. + * + * @param Response $response A Response instance + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), 'headers->set('Surrogate-Control', 'content="ESI/1.0"'); + } + } + + /** + * Checks that the Response needs to be parsed for ESI tags. + * + * @param Response $response A Response instance + * + * @return bool true if the Response needs to be parsed, false otherwise + */ + public function needsParsing(Response $response) + { + if (!$control = $response->headers->get('Surrogate-Control')) { + return false; + } + + return (bool) preg_match('#content="[^"]*ESI/1.0[^"]*"#', $control); + } + + /** + * Renders an ESI 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 = '') + { + $html = sprintf('', + $uri, + $ignoreErrors ? ' onerror="continue"' : '', + $alt ? sprintf(' alt="%s"', $alt) : '' + ); + + if (!empty($comment)) { + return sprintf("\n%s", $comment, $html); + } + + return $html; + } + + /** + * Replaces a Response ESI 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) + { + $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 + if ($response->headers->has('Surrogate-Control')) { + $value = $response->headers->get('Surrogate-Control'); + if ('content="ESI/1.0"' == $value) { + $response->headers->remove('Surrogate-Control'); + } elseif (preg_match('#,\s*content="ESI/1.0"#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#,\s*content="ESI/1.0"#', '', $value)); + } elseif (preg_match('#content="ESI/1.0",\s*#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#content="ESI/1.0",\s*#', '', $value)); + } + } + } + + /** + * Handles an ESI 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) + { + $subRequest = Request::create($uri, '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; + } + } + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/HttpCache.php b/vendor/symfony/http-kernel/HttpCache/HttpCache.php new file mode 100644 index 00000000..2879018c --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/HttpCache.php @@ -0,0 +1,689 @@ + + * + * 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(); + } + } + + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + $this->traces[$request->getMethod().' '.$path] = array(); + + if (!$request->isMethodSafe()) { + $response = $this->invalidate($request, $catch); + } elseif ($request->headers->has('expect')) { + $response = $this->pass($request, $catch); + } else { + $response = $this->lookup($request, $catch); + } + + $this->restoreResponseBody($request, $response); + + $response->setDate(\DateTime::createFromFormat('U', time(), new \DateTimeZone('UTC'))); + + 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) + { + // if allow_reload and no-cache Cache-Control, allow a cache reload + if ($this->options['allow_reload'] && $request->isNoCache()) { + $this->record($request, 'reload'); + + return $this->fetch($request, $catch); + } + + 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 + $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; + } + + /** + * Forwards the Request to the backend and determines whether the response should be stored. + * + * This methods is triggered when the cache missed or a reload is required. + * + * @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 + $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. + * + * @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); + } + + // 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; + } + } + + $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); + + // there is already another process calling the backend + if (true !== $lock) { + // check if we can serve the stale entry + if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) { + $age = $this->options['stale_while_revalidate']; + } + + if (abs($entry->getTtl()) < $age) { + $this->record($request, 'stale-while-revalidate'); + + // server the stale response while there is a revalidation + return true; + } + + // wait for the lock to be released + $wait = 0; + while ($this->store->isLocked($request) && $wait < 5000000) { + usleep(50000); + $wait += 50000; + } + + if ($wait < 5000000) { + // 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; + } + + // we have the lock, call the backend + return false; + } + + /** + * 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) + { + if (!$response->headers->has('Date')) { + $response->setDate(\DateTime::createFromFormat('U', time())); + } + 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) + { + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + $this->traces[$request->getMethod().' '.$path][] = $event; + } +} 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..5f7ee10a --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Ssi.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\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Ssi implements the SSI capabilities to Request and Response instances. + * + * @author Sebastian Krebs + */ +class Ssi implements SurrogateInterface +{ + private $contentTypes; + private $phpEscapeMap = array( + array('', '', '', ''), + ); + + /** + * Constructor. + * + * @param array $contentTypes An array of content-type that should be parsed for SSI 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; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ssi'; + } + + /** + * {@inheritdoc} + */ + 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, 'SSI/1.0'); + } + + /** + * {@inheritdoc} + */ + public function addSurrogateCapability(Request $request) + { + $current = $request->headers->get('Surrogate-Capability'); + $new = 'symfony2="SSI/1.0"'; + + $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); + } + + /** + * {@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 + if ($response->headers->has('Surrogate-Control')) { + $value = $response->headers->get('Surrogate-Control'); + if ('content="SSI/1.0"' == $value) { + $response->headers->remove('Surrogate-Control'); + } elseif (preg_match('#,\s*content="SSI/1.0"#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#,\s*content="SSI/1.0"#', '', $value)); + } elseif (preg_match('#content="SSI/1.0",\s*#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#content="SSI/1.0",\s*#', '', $value)); + } + } + } + + /** + * {@inheritdoc} + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors) + { + $subRequest = Request::create($uri, '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; + } + } + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Store.php b/vendor/symfony/http-kernel/HttpCache/Store.php new file mode 100644 index 00000000..b57d4a77 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Store.php @@ -0,0 +1,489 @@ + + * + * 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. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + public function purge($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..4e628a14 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernel.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\HttpKernel; + +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +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\ConflictingHeadersException; +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; + + /** + * Constructor. + * + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance + * @param RequestStack $requestStack A stack for master/sub requests + */ + public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null) + { + $this->dispatcher = $dispatcher; + $this->resolver = $resolver; + $this->requestStack = $requestStack ?: new RequestStack(); + } + + /** + * {@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 ConflictingHeadersException) { + $e = new BadRequestHttpException('The request headers contain conflicting information regarding the origin of this request.', $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->resolver->getArguments($request, $controller); + + // 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')) { + $response->setStatusCode($response->headers->get('X-Status-Code')); + + $response->headers->remove('X-Status-Code'); + } elseif (!$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..fd42c8e7 --- /dev/null +++ b/vendor/symfony/http-kernel/Kernel.php @@ -0,0 +1,733 @@ + + * + * 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\ParameterBag\ParameterBag; +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\AddClassesToCachePass; +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; + + const VERSION = '3.0.9'; + const VERSION_ID = 30009; + const MAJOR_VERSION = 3; + const MINOR_VERSION = 0; + const RELEASE_VERSION = 9; + const EXTRA_VERSION = ''; + + const END_OF_MAINTENANCE = '07/2016'; + const END_OF_LIFE = '01/2017'; + + /** + * 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)); + } + + 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; + } + + /** + * {@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 + */ + public function loadClassCache($name = 'classes', $extension = '.php') + { + $this->loadClassCache = array($name, $extension); + } + + /** + * Used internally. + */ + public function setClassCache(array $classes) + { + file_put_contents($this->getCacheDir().'/classes.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'; + } + + protected function doLoadClassCache($name, $extension) + { + 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 $bundle) { + $this->bundleMap[$bundle] = $bundleMap; + array_pop($bundleMap); + } + } + } + + /** + * 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()) { + $container = $this->buildContainer(); + $container->compile(); + $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(); + foreach ($this->bundles as $name => $bundle) { + $bundles[$name] = get_class($bundle); + } + + return array_merge( + array( + 'kernel.root_dir' => realpath($this->rootDir) ?: $this->rootDir, + '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.charset' => $this->getCharset(), + 'kernel.container_class' => $this->getContainerClass(), + ), + $this->getEnvParameters() + ); + } + + /** + * Gets the environment parameters. + * + * Only the parameters starting with "SYMFONY__" are considered. + * + * @return array An array of parameters + */ + protected function getEnvParameters() + { + $parameters = array(); + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'SYMFONY__')) { + $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 AddClassesToCachePass($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); + } + + // 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(new ParameterBag($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 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) + { + 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..abbbfcc0 --- /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. The event listener method + * receives a Symfony\Component\HttpKernel\Event\GetResponseEvent + * instance. + * + * @Event + * + * @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. The event listener method receives + * a Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent + * instance. + * + * @Event + * + * @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. The event listener method receives a + * Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent + * instance. + * + * @Event + * + * @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. The event listener method receives a + * Symfony\Component\HttpKernel\Event\FilterControllerEvent instance. + * + * @Event + * + * @var string + */ + const CONTROLLER = 'kernel.controller'; + + /** + * 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. The event listener method receives a + * Symfony\Component\HttpKernel\Event\FilterResponseEvent instance. + * + * @Event + * + * @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. + * The event listener method receives a + * Symfony\Component\HttpKernel\Event\PostResponseEvent instance. + * + * @Event + * + * @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. + * The event listener method receives a + * Symfony\Component\HttpKernel\Event\FinishRequestEvent instance. + * + * @Event + * + * @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..d7308ae5 --- /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. + * + * @return string The application 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..12a74531 --- /dev/null +++ b/vendor/symfony/http-kernel/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/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..29da4abf --- /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) + { + $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) = $values; + $csvStatusCode = isset($values[6]) ? $values[6] : null; + + $csvTime = (int) $csvTime; + + if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method)) { + 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(), + ); + + 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->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..a4e4ba6a --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profile.php @@ -0,0 +1,292 @@ + + * + * 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 The parent Profile + */ + public function setParent(Profile $parent) + { + $this->parent = $parent; + } + + /** + * Returns the parent profile. + * + * @return Profile The parent profile + */ + 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 string The time + */ + public function getTime() + { + if (null === $this->time) { + return 0; + } + + return $this->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 Profile[] An array of Profile + */ + public function getChildren() + { + return $this->children; + } + + /** + * Sets children profiler. + * + * @param Profile[] $children An array of Profile + */ + public function setChildren(array $children) + { + $this->children = array(); + foreach ($children as $child) { + $this->addChild($child); + } + } + + /** + * Adds the child token. + * + * @param Profile $child The child Profile + */ + 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'); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profiler.php b/vendor/symfony/http-kernel/Profiler/Profiler.php new file mode 100644 index 00000000..15223e8e --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profiler.php @@ -0,0 +1,269 @@ + + * + * 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 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 + * + * @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) + { + return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end)); + } + + /** + * 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..57568d39 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Bundle/BundleTest.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\Tests\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 \PHPUnit_Framework_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->getMock('Symfony\Component\Console\Application'); + $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(); + } +} 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..b54d169a --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheClearer/ChainCacheClearerTest.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\Tests\CacheClearer; + +use Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; + +class ChainCacheClearerTest extends \PHPUnit_Framework_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->getMock('Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface'); + } +} 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..e78c8d14 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerAggregateTest.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\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; + +class CacheWarmerAggregateTest extends \PHPUnit_Framework_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..fe5ecb2e --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/CacheWarmer/CacheWarmerTest.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\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerTest extends \PHPUnit_Framework_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..b5d2c9ce --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/ClientTest.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +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; + +class ClientTest extends \PHPUnit_Framework_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; domain=http://example.com; path=/foo; secure; httponly', + 'foo1=bar1; expires=Sun, 15 Feb 2009 20:00:00 GMT; domain=http://example.com; path=/foo; 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..ee5ecce3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Config/EnvParametersResourceTest.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\HttpKernel\Tests\Config; + +use Symfony\Component\HttpKernel\Config\EnvParametersResource; + +class EnvParametersResourceTest extends \PHPUnit_Framework_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..be594862 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Config/FileLocatorTest.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\HttpKernel\Tests\Config; + +use Symfony\Component\HttpKernel\Config\FileLocator; + +class FileLocatorTest extends \PHPUnit_Framework_TestCase +{ + public function testLocate() + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface'); + $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->setExpectedException('LogicException'); + $locator->locate('/some/path'); + } + + public function testLocateWithGlobalResourcePath() + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface'); + $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/ControllerResolverTest.php b/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php new file mode 100644 index 00000000..c591d59f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Controller/ControllerResolverTest.php @@ -0,0 +1,276 @@ + + * + * 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\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; +use Symfony\Component\HttpFoundation\Request; + +class ControllerResolverTest extends \PHPUnit_Framework_TestCase +{ + public function testGetControllerWithoutControllerParameter() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $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(); + $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"'), + ); + } + + 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 + */ + 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->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolver', array('createController')); + $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); + } + + 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/DataCollector/ConfigDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.php new file mode 100644 index 00000000..4a0dc263 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/ConfigDataCollectorTest.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\DataCollector; + +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 \PHPUnit_Framework_TestCase +{ + public function testCollect() + { + $kernel = new KernelForTest('test', true); + $c = new ConfigDataCollector(); + $c->setCacheVersionInfo(false); + $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->assertSame(PHP_VERSION, $c->getPhpVersion()); + $this->assertSame(Kernel::VERSION, $c->getSymfonyVersion()); + $this->assertNull($c->getToken()); + + // if else clause because we don't know it + if (extension_loaded('xdebug')) { + $this->assertTrue($c->hasXDebug()); + } else { + $this->assertFalse($c->hasXDebug()); + } + + // if else clause because we don't know it + if (((extension_loaded('eaccelerator') && ini_get('eaccelerator.enable')) + || + (extension_loaded('apc') && ini_get('apc.enabled')) + || + (extension_loaded('Zend OPcache') && ini_get('opcache.enable')) + || + (extension_loaded('xcache') && ini_get('xcache.cacher')) + || + (extension_loaded('wincache') && ini_get('wincache.ocenabled')))) { + $this->assertTrue($c->hasAccelerator()); + } else { + $this->assertFalse($c->hasAccelerator()); + } + } +} + +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/DumpDataCollectorTest.php b/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.php new file mode 100644 index 00000000..15b77e0c --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/DumpDataCollectorTest.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\Tests\DataCollector; + +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 \PHPUnit_Framework_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:1:{i:0;a:5:{s:4:"data";O:39:"Symfony\Component\VarDumper\Cloner\Data":4:{s:45:"Symfony\Component\VarDumper\Cloner\Datadata";a:1:{i:0;a:1:{i:0;i:123;}}s:49:"Symfony\Component\VarDumper\Cloner\DatamaxDepth";i:%i;s:57:"Symfony\Component\VarDumper\Cloner\DatamaxItemsPerDepth";i:%i;s:54:"Symfony\Component\VarDumper\Cloner\DatauseRefHandles";i:%i;}s:4:"name";s:25:"DumpDataCollectorTest.php";s:4:"file";s:%a', + str_replace("\0", '', $collector->serialize()) + ); + + $this->assertSame(0, $collector->getDumpsCount()); + $this->assertSame('a:0:{}', $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, $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..6c71f4c9 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/ExceptionDataCollectorTest.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\Tests\DataCollector; + +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 \PHPUnit_Framework_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..4c1f380d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/LoggerDataCollectorTest.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\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; + +class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getCollectTestData + */ + public function testCollect($nb, $logs, $expectedLogs, $expectedDeprecationCount, $expectedScreamCount, $expectedPriorities = null) + { + $logger = $this->getMock('Symfony\Component\HttpKernel\Log\DebugLoggerInterface'); + $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->assertSame('logger', $c->getName()); + $this->assertSame($nb, $c->countErrors()); + $this->assertSame($expectedLogs ?: $logs, $c->getLogs()); + $this->assertSame($expectedDeprecationCount, $c->countDeprecations()); + $this->assertSame($expectedScreamCount, $c->countScreams()); + + if (isset($expectedPriorities)) { + $this->assertSame($expectedPriorities, $c->getPriorities()); + } + } + + public function getCollectTestData() + { + return array( + array( + 1, + array(array('message' => 'foo', 'context' => array(), 'priority' => 100, 'priorityName' => 'DEBUG')), + null, + 0, + 0, + ), + array( + 1, + array(array('message' => 'foo', 'context' => array('foo' => fopen(__FILE__, 'r')), 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo', 'context' => array('foo' => 'Resource(stream)'), 'priority' => 100, 'priorityName' => 'DEBUG')), + 0, + 0, + ), + array( + 1, + array(array('message' => 'foo', 'context' => array('foo' => new \stdClass()), 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo', 'context' => array('foo' => 'Object(stdClass)'), 'priority' => 100, 'priorityName' => 'DEBUG')), + 0, + 0, + ), + array( + 1, + array( + array('message' => 'foo', 'context' => array('type' => E_DEPRECATED, 'level' => E_ALL), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo2', 'context' => array('type' => E_USER_DEPRECATED, 'level' => E_ALL), 'priority' => 100, 'priorityName' => 'DEBUG'), + ), + null, + 2, + 0, + array(100 => array('count' => 2, 'name' => 'DEBUG')), + ), + array( + 1, + array(array('message' => 'foo3', 'context' => array('name' => 'E_USER_WARNING', 'type' => E_USER_WARNING, 'level' => 0, 'file' => __FILE__, 'line' => 123), 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo3', 'context' => array('name' => 'E_USER_WARNING', 'type' => E_USER_WARNING, 'level' => 0, 'file' => __FILE__, 'line' => 123, 'scream' => true), 'priority' => 100, 'priorityName' => 'DEBUG')), + 0, + 1, + ), + array( + 1, + array( + array('message' => 'foo3', 'context' => array('type' => E_USER_WARNING, 'level' => 0, 'file' => __FILE__, 'line' => 123), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo3', 'context' => array('type' => E_USER_WARNING, 'level' => -1, 'file' => __FILE__, 'line' => 123), 'priority' => 100, 'priorityName' => 'DEBUG'), + ), + array(array('message' => 'foo3', 'context' => array('name' => 'E_USER_WARNING', 'type' => E_USER_WARNING, 'level' => -1, 'file' => __FILE__, 'line' => 123, 'errorCount' => 2), 'priority' => 100, 'priorityName' => 'DEBUG')), + 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..340b4288 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/MemoryDataCollectorTest.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\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class MemoryDataCollectorTest extends \PHPUnit_Framework_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..2eb1c41e --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -0,0 +1,222 @@ + + * + * 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 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 \PHPUnit_Framework_TestCase +{ + public function testCollect() + { + $c = new RequestDataCollector(); + + $c->collect($this->createRequest(), $this->createResponse()); + + $attributes = $c->getRequestAttributes(); + + $this->assertSame('request', $c->getName()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\HeaderBag', $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->assertSame('html', $c->getFormat()); + $this->assertSame('foobar', $c->getRoute()); + $this->assertSame(array('name' => 'foo'), $c->getRouteParams()); + $this->assertSame(array(), $c->getSessionAttributes()); + $this->assertSame('en', $c->getLocale()); + $this->assertRegExp('/Resource\(stream#\d+\)/', $attributes->get('resource')); + $this->assertSame('Object(stdClass)', $attributes->get('object')); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\HeaderBag', $c->getResponseHeaders()); + $this->assertSame('OK', $c->getStatusText()); + $this->assertSame(200, $c->getStatusCode()); + $this->assertSame('application/json', $c->getContentType()); + } + + /** + * Test various types of controller callables. + */ + public function testControllerInspection() + { + // 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 + $controllerTests = array( + array( + '"Regular" callable', + array($this, 'testControllerInspection'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\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', + 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest::staticControllerMethod', + 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest::staticControllerMethod', + ), + + 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(), + ), + ), + ); + + $c = new RequestDataCollector(); + $request = $this->createRequest(); + $response = $this->createResponse(); + foreach ($controllerTests as $controllerTest) { + $this->injectController($c, $controllerTest[1], $request); + $c->collect($request, $response); + $this->assertSame($controllerTest[2], $c->getController(), sprintf('Testing: %s', $controllerTest[0])); + } + } + + protected function createRequest() + { + $request = Request::create('http://test.com/foo?bar=baz'); + $request->attributes->set('foo', 'bar'); + $request->attributes->set('_route', 'foobar'); + $request->attributes->set('_route_params', array('name' => 'foo')); + $request->attributes->set('resource', fopen(__FILE__, 'r')); + $request->attributes->set('object', new \stdClass()); + + return $request; + } + + protected function createResponse() + { + $response = new Response(); + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'application/json'); + $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->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $httpKernel = new HttpKernel(new EventDispatcher(), $resolver); + $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..0bdcccd5 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/TimeDataCollectorTest.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\Tests\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class TimeDataCollectorTest extends \PHPUnit_Framework_TestCase +{ + public function testCollect() + { + $c = new TimeDataCollector(); + + $request = new Request(); + $request->server->set('REQUEST_TIME', 1); + + $c->collect($request, new Response()); + + $this->assertEquals(1000, $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->getMock('Symfony\Component\HttpKernel\KernelInterface'); + $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..09810a98 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DataCollector/Util/ValueExporterTest.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\HttpKernel\Tests\DataCollector\Util; + +use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; + +class ValueExporterTest extends \PHPUnit_Framework_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+0000', $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+0000', $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/TraceableEventDispatcherTest.php b/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php new file mode 100644 index 00000000..f64d7247 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcher; +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 \PHPUnit_Framework_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', + '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) + { + $resolver = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface'); + $resolver->expects($this->once())->method('getController')->will($this->returnValue($controller)); + $resolver->expects($this->once())->method('getArguments')->will($this->returnValue(array())); + + return new HttpKernel($dispatcher, $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..98266e94 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/FragmentRendererPassTest.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\HttpKernel\Tests\DependencyInjection; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +class FragmentRendererPassTest extends \PHPUnit_Framework_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->getMock('Symfony\Component\DependencyInjection\Definition'); + + $builder = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('hasDefinition', 'findTaggedServiceIds', 'getDefinition') + ); + $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 = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $renderer + ->expects($this->once()) + ->method('addMethodCall') + ->with('addRendererService', array('foo', 'my_content_renderer')) + ; + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\HttpKernel\Tests\DependencyInjection\RendererService')); + $definition + ->expects($this->once()) + ->method('isPublic') + ->will($this->returnValue(true)) + ; + + $builder = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('hasDefinition', 'findTaggedServiceIds', 'getDefinition') + ); + $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)); + + $pass = new FragmentRendererPass(); + $pass->process($builder); + } +} + +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..34bd87a9 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/LazyLoadingFragmentHandlerTest.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\DependencyInjection; + +use Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class LazyLoadingFragmentHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function test() + { + $renderer = $this->getMock('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface'); + $renderer->expects($this->once())->method('getName')->will($this->returnValue('foo')); + $renderer->expects($this->any())->method('render')->will($this->returnValue(new Response())); + + $requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack'); + $requestStack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(Request::create('/'))); + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $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'); + } +} 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..895973d7 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.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\DependencyInjection; + +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; + +class MergeExtensionConfigurationPassTest extends \PHPUnit_Framework_TestCase +{ + public function testAutoloadMainExtension() + { + $container = $this->getMock( + 'Symfony\\Component\\DependencyInjection\\ContainerBuilder', + array( + 'getExtensionConfig', + 'loadFromExtension', + 'getParameterBag', + 'getDefinitions', + 'getAliases', + 'getExtensions', + ) + ); + $params = $this->getMock('Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag'); + + $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/EventListener/AddRequestFormatsListenerTest.php b/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.php new file mode 100644 index 00000000..3a82ce0f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/AddRequestFormatsListenerTest.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\HttpKernel\Tests\EventListener; + +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 \PHPUnit_Framework_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 => 'onKernelRequest'), + 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->getMock('Symfony\Component\HttpFoundation\Request'); + } + + 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..8e1c35b3 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/DebugHandlersListenerTest.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 Symfony\Component\HttpKernel\Tests\EventListener; + +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 \PHPUnit_Framework_TestCase +{ + public function testConfigure() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'), + 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->getMock('Symfony\Component\Console\Application'); + $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..9bbdaded --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/DumpListenerTest.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\Tests\EventListener; + +use Symfony\Component\HttpKernel\EventListener\DumpListener; +use Symfony\Component\HttpKernel\KernelEvents; +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 \PHPUnit_Framework_TestCase +{ + public function testSubscribedEvents() + { + $this->assertSame( + array(KernelEvents::REQUEST => 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($var.'-')); + } +} + +class MockDumper implements DataDumperInterface +{ + public function dump(Data $data) + { + $rawData = $data->getRawData(); + + echo '+'.$rawData[0]; + } +} 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..947af112 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ExceptionListenerTest.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\EventListener; + +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 \PHPUnit_Framework_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->getMock('Psr\Log\LoggerInterface')); + + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $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..7cfce98f --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/FragmentListenerTest.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\EventListener; + +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 \PHPUnit_Framework_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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'), $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..a23268ca --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.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\Tests\EventListener; + +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 \PHPUnit_Framework_TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack', array(), array(), '', false); + } + + 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->getMock('Symfony\Component\Routing\RequestContext'); + $context->expects($this->once())->method('setParameter')->with('_locale', 'es'); + + $router = $this->getMock('Symfony\Component\Routing\Router', array('getContext'), array(), '', false); + $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->getMock('Symfony\Component\Routing\RequestContext'); + $context->expects($this->once())->method('setParameter')->with('_locale', 'es'); + + $router = $this->getMock('Symfony\Component\Routing\Router', array('getContext'), array(), '', false); + $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->getMock('Symfony\Component\HttpKernel\Event\FinishRequestEvent', array(), array(), '', false); + + $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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'), $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..d452ceb1 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.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\HttpKernel\Tests\EventListener; + +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 \PHPUnit_Framework_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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + + $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..821688ef --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.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\EventListener; + +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 \PHPUnit_Framework_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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + } + + 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..9eb1ec96 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.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\HttpKernel\Tests\EventListener; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\Routing\RequestContext; + +class RouterListenerTest extends \PHPUnit_Framework_TestCase +{ + private $requestStack; + + protected function setUp() + { + $this->requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack', array(), array(), '', false); + } + + /** + * @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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $request = Request::create('http://localhost/'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMock('Symfony\Component\Routing\Matcher\RequestMatcherInterface'); + $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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $request = Request::create('http://localhost/', 'post'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMock('Symfony\Component\Routing\Matcher\RequestMatcherInterface'); + $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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $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) + { + $requestMatcher = $this->getMock('Symfony\Component\Routing\Matcher\RequestMatcherInterface'); + $requestMatcher->expects($this->once()) + ->method('matchRequest') + ->will($this->returnValue($parameter)); + + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $logger->expects($this->once()) + ->method('info') + ->with($this->equalTo($log)); + + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $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 "foo".'), + array(array(), 'Matched route "n/a".'), + ); + } +} 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..1a0acf92 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.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\EventListener; + +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 \PHPUnit_Framework_TestCase +{ + public function testFilterDoesNothingForSubRequests() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $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..cbaaf5f9 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.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\HttpKernel\Tests\EventListener; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * SessionListenerTest. + * + * Tests SessionListener. + * + * @author Bulat Shakirzyanov + */ +class TestSessionListenerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var TestSessionListener + */ + private $listener; + + /** + * @var SessionInterface + */ + private $session; + + protected function setUp() + { + $this->listener = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\EventListener\TestSessionListener'); + $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()); + } + + private function filterResponse(Request $request, $type = HttpKernelInterface::MASTER_REQUEST) + { + $request->setSession($this->session); + $response = new Response(); + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $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..c37d6478 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +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 \PHPUnit_Framework_TestCase +{ + private $listener; + private $translator; + private $requestStack; + + protected function setUp() + { + $this->translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface'); + $this->requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack'); + $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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + } + + 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..842a3869 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/EventListener/ValidateRequestListenerTest.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\EventListener; + +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 \PHPUnit_Framework_TestCase +{ + /** + * @expectedException Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException + */ + public function testListenerThrowsWhenMasterRequestHasInconsistentClientIps() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + + $request = new Request(); + $request->setTrustedProxies(array('1.1.1.1')); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('FORWARDED', '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/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/VariadicController.php b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php new file mode 100644 index 00000000..a540f9d1 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fixtures/Controller/VariadicController.php @@ -0,0 +1,10 @@ + + * + * 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/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..88c92b67 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/EsiFragmentRendererTest.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 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 \PHPUnit_Framework_TestCase +{ + public function testRenderFallbackToInlineStrategyIfEsiNotSupported() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + 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..79fee3fc --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/FragmentHandlerTest.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\Tests\Fragment; + +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * @group time-sensitive + */ +class FragmentHandlerTest extends \PHPUnit_Framework_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->getMock('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface'); + $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..2f266dba --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/HIncludeFragmentRendererTest.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\Fragment; + +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 \PHPUnit_Framework_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->getMock('Symfony\\Component\\Templating\\EngineInterface'); + $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..d14ebb0d --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -0,0 +1,223 @@ + + * + * 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 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 \PHPUnit_Framework_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)); + + $strategy->render(new ControllerReference('main_controller', array('object' => $object), array()), Request::create('/')); + } + + public function testRenderWithObjectsAsAttributesPassedAsObjectsInTheController() + { + $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver', array('getController')); + $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); + $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() + { + $trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP); + + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, ''); + + $strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/'))); + $strategy->render('/', Request::create('/')); + + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $trustedHeaderName); + } + + /** + * @expectedException \RuntimeException + */ + public function testRenderExceptionNoIgnoreErrors() + { + $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $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->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $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->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $kernel + ->expects($this->any()) + ->method('handle') + ->will($returnValue) + ; + + return $kernel; + } + + /** + * 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) + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $kernel + ->expects($this->any()) + ->method('handle') + ->with($this->equalTo($request, 1)) + ; + + return $kernel; + } + + public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() + { + $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $resolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function () { + ob_start(); + echo 'bar'; + throw new \RuntimeException(); + })) + ; + $resolver + ->expects($this->once()) + ->method('getArguments') + ->will($this->returnValue(array())) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $resolver); + $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::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { + $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() + { + $trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, ''); + + $this->testESIHeaderIsKeptInSubrequest(); + + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $trustedHeaderName); + } + + public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest() + { + $expectedSubRequest = Request::create('/'); + if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { + $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); + } +} + +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..1b5f4711 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Fragment/RoutableFragmentRendererTest.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\HttpKernel\Tests\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +class RoutableFragmentRendererTest extends \PHPUnit_Framework_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/HttpCache/EsiTest.php b/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.php new file mode 100644 index 00000000..0d52ce89 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/EsiTest.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\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class EsiTest extends \PHPUnit_Framework_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('symfony2="ESI/1.0"', $request->headers->get('Surrogate-Capability')); + + $esi->addSurrogateCapability($request); + $this->assertEquals('symfony2="ESI/1.0", symfony2="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->getMock('Symfony\Component\HttpKernel\HttpCache\HttpCache', array('getRequest', 'handle'), array(), '', false); + $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..0498f219 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTest.php @@ -0,0 +1,1263 @@ + + * + * 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 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 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 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); + + $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()); + } +} + +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..722e9876 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/HttpCacheTestCase.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\HttpKernel\Tests\HttpCache; + +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 \PHPUnit_Framework_TestCase +{ + protected $kernel; + protected $cache; + protected $caches; + protected $cacheConfig; + protected $request; + protected $response; + protected $responses; + protected $catch; + protected $esi; + 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..41e40962 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/ResponseCacheStrategyTest.php @@ -0,0 +1,77 @@ + + * + * 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 Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategy; + +class ResponseCacheStrategyTest extends \PHPUnit_Framework_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..07b70dce --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/SsiTest.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\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Ssi; + +class SsiTest extends \PHPUnit_Framework_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('symfony2="SSI/1.0"', $request->headers->get('Surrogate-Capability')); + + $ssi->addSurrogateCapability($request); + $this->assertEquals('symfony2="SSI/1.0", symfony2="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->getMock('Symfony\Component\HttpKernel\HttpCache\HttpCache', array('getRequest', 'handle'), array(), '', false); + $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..bf4239be --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/StoreTest.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\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Store; + +class StoreTest extends \PHPUnit_Framework_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)); + } + + 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..5546ba2e --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/TestHttpKernel.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\HttpCache; + +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 +{ + 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); + } + + 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..5b5209e9 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpCache/TestMultipleHttpKernel.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\HttpKernel\Tests\HttpCache; + +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 +{ + 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); + } + + 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..a392e3b7 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/HttpKernelTest.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; + +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 \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsTrue() + { + $kernel = new HttpKernel(new EventDispatcher(), $this->getResolver(function () { throw new \RuntimeException(); })); + + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + } + + /** + * @expectedException \RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndCatchIsFalseAndNoListenerIsRegistered() + { + $kernel = new HttpKernel(new EventDispatcher(), $this->getResolver(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 = new HttpKernel($dispatcher, $this->getResolver(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 = new HttpKernel($dispatcher, $this->getResolver(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 = new HttpKernel($dispatcher, $this->getResolver(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 = new HttpKernel($dispatcher, $this->getResolver(function () { throw new MethodNotAllowedHttpException(array('POST')); })); + $response = $kernel->handle(new Request()); + + $this->assertEquals('405', $response->getStatusCode()); + $this->assertEquals('POST', $response->headers->get('Allow')); + } + + /** + * @dataProvider getStatusCodes + */ + public function testHandleWhenAnExceptionIsHandledWithASpecificStatusCode($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 = new HttpKernel($dispatcher, $this->getResolver(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), + ); + } + + public function testHandleWhenAListenerReturnsAResponse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->setResponse(new Response('hello')); + }); + + $kernel = new HttpKernel($dispatcher, $this->getResolver()); + + $this->assertEquals('hello', $kernel->handle(new Request())->getContent()); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testHandleWhenNoControllerIsFound() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(false)); + + $kernel->handle(new Request()); + } + + public function testHandleWhenTheControllerIsAClosure() + { + $response = new Response('foo'); + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(function () use ($response) { return $response; })); + + $this->assertSame($response, $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnObjectWithInvoke() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(new Controller())); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAFunction() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver('Symfony\Component\HttpKernel\Tests\controller_func')); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnArray() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(array(new Controller(), 'controller'))); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAStaticArray() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(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 = new HttpKernel($dispatcher, $this->getResolver(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 = new HttpKernel($dispatcher, $this->getResolver(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 = new HttpKernel($dispatcher, $this->getResolver()); + + $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); + } + + public function testTerminate() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver()); + $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->getMock('Symfony\Component\HttpFoundation\RequestStack', array('push', 'pop')); + $stack->expects($this->at(0))->method('push')->with($this->equalTo($request)); + $stack->expects($this->at(1))->method('pop'); + + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(), $stack); + + $kernel->handle($request, HttpKernelInterface::MASTER_REQUEST); + } + + /** + * @expectedException Symfony\Component\HttpKernel\Exception\BadRequestHttpException + */ + public function testInconsistentClientIpsOnMasterRequests() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->getRequest()->getClientIp(); + }); + + $kernel = new HttpKernel($dispatcher, $this->getResolver()); + + $request = new Request(); + $request->setTrustedProxies(array('1.1.1.1')); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $request->headers->set('FORWARDED', '2.2.2.2'); + $request->headers->set('X_FORWARDED_FOR', '3.3.3.3'); + + $kernel->handle($request, $kernel::MASTER_REQUEST, false); + } + + protected function getResolver($controller = null) + { + if (null === $controller) { + $controller = function () { return new Response('Hello'); }; + } + + $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $resolver->expects($this->any()) + ->method('getController') + ->will($this->returnValue($controller)); + $resolver->expects($this->any()) + ->method('getArguments') + ->will($this->returnValue(array())); + + return $resolver; + } + + protected 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..2eacb8ae --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/KernelTest.php @@ -0,0 +1,816 @@ + + * + * 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\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; + +class KernelTest extends \PHPUnit_Framework_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->getMock('Symfony\Component\HttpKernel\Bundle\Bundle'); + $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()); + } + + 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(); + } + + 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->getMock('Symfony\Component\HttpKernel\Bundle\Bundle'); + $bundle->expects($this->once()) + ->method('shutdown'); + + $kernel = $this->getKernel(array(), array($bundle)); + + $kernel->boot(); + $kernel->shutdown(); + } + + public function testShutdownGivesNullContainerToAllBundles() + { + $bundle = $this->getMock('Symfony\Component\HttpKernel\Bundle\Bundle'); + $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()); + } + + /** + * 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) + { + } +} 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..6f53de83 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Profiler/FileProfilerStorageTest.php @@ -0,0 +1,335 @@ + + * + * 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 Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profile; + +class FileProfilerStorageTest extends \PHPUnit_Framework_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 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..6e56f8bc --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/Profiler/ProfilerTest.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\Tests\Profiler; + +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 \PHPUnit_Framework_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); + + $this->assertSame(204, $profile->getStatusCode()); + $this->assertSame('GET', $profile->getMethod()); + $this->assertEquals(array('foo' => 'bar'), $profiler->get('request')->getRequestQuery()->all()); + } + + 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', '')); + } + + 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..d526c4de --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/TestHttpKernel.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; + +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 +{ + public function __construct() + { + parent::__construct(new EventDispatcher(), $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..10f41b34 --- /dev/null +++ b/vendor/symfony/http-kernel/Tests/UriSignerTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\UriSigner; + +class UriSignerTest extends \PHPUnit_Framework_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'))); + } +} diff --git a/vendor/symfony/http-kernel/UriSigner.php b/vendor/symfony/http-kernel/UriSigner.php new file mode 100644 index 00000000..c2d0d796 --- /dev/null +++ b/vendor/symfony/http-kernel/UriSigner.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; + +/** + * Signs URIs. + * + * @author Fabien Potencier + */ +class UriSigner +{ + private $secret; + + /** + * Constructor. + * + * @param string $secret A secret + */ + public function __construct($secret) + { + $this->secret = $secret; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding a _hash 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, '?') ? '?' : '&').'_hash='.$this->computeHash($uri); + } + + /** + * Checks that a URI contains the correct hash. + * + * The _hash 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['_hash'])) { + return false; + } + + $hash = urlencode($params['_hash']); + unset($params['_hash']); + + 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..eaa3fe03 --- /dev/null +++ b/vendor/symfony/http-kernel/composer.json @@ -0,0 +1,66 @@ +{ + "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": "~2.8.8|~3.0.8|~3.1.2|~3.2", + "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": "~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/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": "~2.8|~3.0" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "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.0-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..b29969b3 --- /dev/null +++ b/vendor/symfony/http-kernel/phpunit.xml.dist @@ -0,0 +1,38 @@ + + + + + + + + + + ./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..04ac1d31 --- /dev/null +++ b/vendor/symfony/routing/CHANGELOG.md @@ -0,0 +1,200 @@ +CHANGELOG +========= + +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..3eca9ecd --- /dev/null +++ b/vendor/symfony/routing/CompiledRoute.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\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) + { + $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/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..a191bd0e --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGenerator.php @@ -0,0 +1,326 @@ + + * + * 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; + 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].'$#', $mergedParams[$token[3]])) { + $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); + if ($this->strictRequirements) { + throw new InvalidParameterException($message); + } + + if ($this->logger) { + $this->logger->error($message); + } + + 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', $mergedParams[$token[3]])) { + $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); + + if ($this->strictRequirements) { + throw new InvalidParameterException($message); + } + + if ($this->logger) { + $this->logger->error($message); + } + + 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; + }); + + if ($extra && $query = http_build_query($extra, '', '&')) { + // "/" and "?" can be left decoded for better user experience, see + // http://tools.ietf.org/html/rfc3986#section-3.4 + $url .= '?'.strtr($query, array('%2F' => '/')); + } + + 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..f501ebd9 --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGeneratorInterface.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\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. + * + * @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..12a74531 --- /dev/null +++ b/vendor/symfony/routing/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/routing/Loader/AnnotationClassLoader.php b/vendor/symfony/routing/Loader/AnnotationClassLoader.php new file mode 100644 index 00000000..565698e8 --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationClassLoader.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\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); + } + } + } + + 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 (!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..f66b9289 --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.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\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 \RecursiveDirectoryIterator($dir), \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..b8fc0361 --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationFileLoader.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\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)); + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + + if (!isset($token[1])) { + continue; + } + + if (true === $class && T_STRING === $token[0]) { + return $namespace.'\\'.$token[1]; + } + + if (true === $namespace && T_STRING === $token[0]) { + $namespace = $token[1]; + while (isset($tokens[++$i][1]) && in_array($tokens[$i][0], array(T_NS_SEPARATOR, T_STRING))) { + $namespace .= $tokens[$i][1]; + } + $token = $tokens[$i]; + } + + if (T_CLASS === $token[0]) { + // Skip usage of ::class constant + $isClassConstant = false; + for ($j = $i - 1; $j > 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..69382571 --- /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 Symfony\Component\DependencyInjection\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..43d04ea7 --- /dev/null +++ b/vendor/symfony/routing/Loader/XmlFileLoader.php @@ -0,0 +1,241 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace 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) { + switch ($n->localName) { + case 'default': + if ($this->isElementValueNull($n)) { + $defaults[$n->getAttribute('key')] = null; + } else { + $defaults[$n->getAttribute('key')] = trim($n->textContent); + } + + 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); + } + + 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..31314011 --- /dev/null +++ b/vendor/symfony/routing/Loader/YamlFileLoader.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace 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; + +/** + * 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)); + } 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..8090c784 --- /dev/null +++ b/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php b/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php new file mode 100644 index 00000000..0f2815b7 --- /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 DumperCollection[]|DumperRoute[] Array of DumperCollection|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 DumperCollection The root collection + */ + public function getRoot() + { + return (null !== $this->parent) ? $this->parent->getRoot() : $this; + } + + /** + * Returns the parent collection. + * + * @return DumperCollection|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/DumperPrefixCollection.php b/vendor/symfony/routing/Matcher/Dumper/DumperPrefixCollection.php new file mode 100644 index 00000000..dd1a0d90 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/DumperPrefixCollection.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\Routing\Matcher\Dumper; + +/** + * Prefix tree of routes preserving routes order. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperPrefixCollection extends DumperCollection +{ + /** + * @var string + */ + private $prefix = ''; + + /** + * Returns the prefix. + * + * @return string The prefix + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * Sets the prefix. + * + * @param string $prefix The prefix + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * Adds a route in the tree. + * + * @param DumperRoute $route The route + * + * @return DumperPrefixCollection The node the route was added to + * + * @throws \LogicException + */ + public function addPrefixRoute(DumperRoute $route) + { + $prefix = $route->getRoute()->compile()->getStaticPrefix(); + + for ($collection = $this; null !== $collection; $collection = $collection->getParent()) { + // Same prefix, add to current leave + if ($collection->prefix === $prefix) { + $collection->add($route); + + return $collection; + } + + // Prefix starts with route's prefix + if ('' === $collection->prefix || 0 === strpos($prefix, $collection->prefix)) { + $child = new self(); + $child->setPrefix(substr($prefix, 0, strlen($collection->prefix) + 1)); + $collection->add($child); + + return $child->addPrefixRoute($route); + } + } + + // Reached only if the root has a non empty prefix + throw new \LogicException('The collection root must not have a prefix'); + } + + /** + * Merges nodes whose prefix ends with a slash. + * + * Children of a node whose prefix ends with a slash are moved to the parent node + */ + public function mergeSlashNodes() + { + $children = array(); + + foreach ($this as $child) { + if ($child instanceof self) { + $child->mergeSlashNodes(); + if ('/' === substr($child->prefix, -1)) { + $children = array_merge($children, $child->all()); + } else { + $children[] = $child; + } + } else { + $children[] = $child; + } + } + + $this->setAll($children); + } +} 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..aa7df8a7 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php @@ -0,0 +1,409 @@ + + * + * 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; + +$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 = \$this->context->getHost();\n\n"; + $fetchedHost = true; + } + + $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true)); + } + + $tree = $this->buildPrefixTree($collection); + $groupCode = $this->compilePrefixRoutes($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; + } + + /** + * Generates PHP code recursively to match a tree of routes. + * + * @param DumperPrefixCollection $collection A DumperPrefixCollection instance + * @param bool $supportsRedirections Whether redirections are supported by the base class + * @param string $parentPrefix Prefix of the parent collection + * + * @return string PHP code + */ + private function compilePrefixRoutes(DumperPrefixCollection $collection, $supportsRedirections, $parentPrefix = '') + { + $code = ''; + $prefix = $collection->getPrefix(); + $optimizable = 1 < strlen($prefix) && 1 < count($collection->all()); + $optimizedPrefix = $parentPrefix; + + if ($optimizable) { + $optimizedPrefix = $prefix; + + $code .= sprintf(" if (0 === strpos(\$pathinfo, %s)) {\n", var_export($prefix, true)); + } + + foreach ($collection as $route) { + if ($route instanceof DumperCollection) { + $code .= $this->compilePrefixRoutes($route, $supportsRedirections, $optimizedPrefix); + } else { + $code .= $this->compileRoute($route->getRoute(), $route->getName(), $supportsRedirections, $optimizedPrefix)."\n"; + } + } + + if ($optimizable) { + $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(); + + // GET and HEAD are equivalent + if (in_array('GET', $methods) && !in_array('HEAD', $methods)) { + $methods[] = 'HEAD'; + } + + $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods)); + + if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#', $compiledRoute->getRegex(), $m)) { + if ($supportsTrailingSlash && substr($m['url'], -1) === '/') { + $conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); + $hasTrailingSlash = true; + } else { + $conditions[] = sprintf('$pathinfo === %s', 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)); + } + + $regex = $compiledRoute->getRegex(); + 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 .= <<context->getMethod() != '$methods[0]') { + \$allow[] = '$methods[0]'; + goto $gotoname; + } + + +EOF; + } else { + $methods = implode("', '", $methods); + $code .= <<context->getMethod(), array('$methods'))) { + \$allow = array_merge(\$allow, array('$methods')); + goto $gotoname; + } + + +EOF; + } + } + + if ($hasTrailingSlash) { + $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 .= <<context->getScheme()])) { + return \$this->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; + } + + /** + * Organizes the routes into a prefix tree. + * + * Routes order is preserved such that traversing the tree will traverse the + * routes in the origin order. + * + * @param DumperCollection $collection A collection of routes + * + * @return DumperPrefixCollection + */ + private function buildPrefixTree(DumperCollection $collection) + { + $tree = new DumperPrefixCollection(); + $current = $tree; + + foreach ($collection as $route) { + $current = $current->addPrefixRoute($route); + } + + $tree->mergeSlashNodes(); + + return $tree; + } + + 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/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..862b824d --- /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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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 RequestContext The current instance, implementing a fluent interface + */ + 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..ad006961 --- /dev/null +++ b/vendor/symfony/routing/Route.php @@ -0,0 +1,588 @@ + + * + * 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) + * + * @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) + { + $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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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 Route The current Route instance + */ + 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..5f0256ac --- /dev/null +++ b/vendor/symfony/routing/RouteCollectionBuilder.php @@ -0,0 +1,372 @@ + + * + * 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 RouteCollectionBuilder + * + * @throws FileLoaderLoadException + */ + public function import($resource, $prefix = '/', $type = null) + { + /** @var RouteCollection $collection */ + $collection = $this->load($resource, $type); + + // create a builder from the RouteCollection + $builder = $this->createBuilder(); + 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 RouteCollectionBuilder + */ + 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 opiton 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)) { + return $this->loader->load($resource, $type); + } + + if (null === $resolver = $this->loader->getResolver()) { + throw new FileLoaderLoadException($resource); + } + + if (false === $loader = $resolver->resolve($resource, $type)) { + throw new FileLoaderLoadException($resource); + } + + return $loader->load($resource, $type); + } +} diff --git a/vendor/symfony/routing/RouteCompiler.php b/vendor/symfony/routing/RouteCompiler.php new file mode 100644 index 00000000..f6637da6 --- /dev/null +++ b/vendor/symfony/routing/RouteCompiler.php @@ -0,0 +1,229 @@ + + * + * 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 = '/,;.:-_~+*=@|'; + + /** + * {@inheritdoc} + * + * @throws \LogicException If a variable is referenced more than once + * @throws \DomainException If a variable name is numeric because PHP raises an error for such + * subpatterns in PCRE and thus would break matching, e.g. "(?P<123>.+)". + */ + 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']; + $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 ? '.' : '/'; + + // 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]); + $precedingChar = strlen($precedingText) > 0 ? substr($precedingText, -1) : ''; + $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); + + if (is_numeric($varName)) { + throw new \DomainException(sprintf('Variable name "%s" cannot be numeric 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 ($isSeparator && strlen($precedingText) > 1) { + $tokens[] = array('text', substr($precedingText, 0, -1)); + } 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); + $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 .= '+'; + } + } + + $tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName); + $variables[] = $varName; + } + + if ($pos < strlen($pattern)) { + $tokens[] = array('text', substr($pattern, $pos)); + } + + // find the first optional token + $firstOptional = PHP_INT_MAX; + if (!$isHost) { + for ($i = count($tokens) - 1; $i >= 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); + } + + return array( + 'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '', + 'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s'.($isHost ? 'i' : ''), + 'tokens' => array_reverse($tokens), + 'variables' => $variables, + ); + } + + /** + * Returns the next static character in the Route pattern that will serve as a separator. + * + * @param string $pattern The route pattern + * + * @return string The next static character that functions as separator (or empty string when none available) + */ + private static function findNextSeparator($pattern) + { + 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 + $pattern = preg_replace('#\{\w+\}#', '', $pattern); + + return isset($pattern[0]) && 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..be4c5010 --- /dev/null +++ b/vendor/symfony/routing/Router.php @@ -0,0 +1,378 @@ + + * + * 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) + * * resource_type: Type hint for the main resource (optional) + * + * @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..7a3665ee --- /dev/null +++ b/vendor/symfony/routing/Tests/Annotation/RouteTest.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\Annotation; + +use Symfony\Component\Routing\Annotation\Route; + +class RouteTest extends \PHPUnit_Framework_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..215ebb70 --- /dev/null +++ b/vendor/symfony/routing/Tests/CompiledRouteTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use Symfony\Component\Routing\CompiledRoute; + +class CompiledRouteTest extends \PHPUnit_Framework_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/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/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/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..4ea0b8a1 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher1.php @@ -0,0 +1,312 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $context = $this->context; + $request = $this->request; + + // foo + if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + if (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + 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 (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + goto not_barhead; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + } + not_barhead: + + } + + if (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ($pathinfo === '/test/baz') { + return array('_route' => 'baz'); + } + + // baz2 + if ($pathinfo === '/test/baz.html') { + return array('_route' => 'baz2'); + } + + // baz3 + if ($pathinfo === '/test/baz3/') { + 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 ($this->context->getMethod() != 'POST') { + $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 ($this->context->getMethod() != 'PUT') { + $allow[] = 'PUT'; + goto not_bazbaz6; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + } + not_bazbaz6: + + } + + // foofoo + if ($pathinfo === '/foofoo') { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + // quoter + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ($pathinfo === '/spa ce') { + 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 ()); + } + + } + + } + + if (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!',)); + } + + // overridden2 + if ($pathinfo === '/multi/new') { + return array('_route' => 'overridden2'); + } + + // hey + if ($pathinfo === '/multi/hey/') { + return array('_route' => 'hey'); + } + + } + + // 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 ($pathinfo === '/ababa') { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $this->context->getHost(); + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route1 + if ($pathinfo === '/route1') { + return array('_route' => 'route1'); + } + + // route2 + if ($pathinfo === '/c2/route2') { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) { + // route3 + if ($pathinfo === '/c2/route3') { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route4 + if ($pathinfo === '/route4') { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route5 + if ($pathinfo === '/route5') { + return array('_route' => 'route5'); + } + + } + + // route6 + if ($pathinfo === '/route6') { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ($pathinfo === '/route11') { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ($pathinfo === '/route12') { + 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 ()); + } + + } + + if (0 === strpos($pathinfo, '/route1')) { + // 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 ($pathinfo === '/route17') { + return array('_route' => 'route17'); + } + + } + + if (0 === strpos($pathinfo, '/a')) { + // a + if ($pathinfo === '/a/a...') { + 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..f9d3fa2d --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher2.php @@ -0,0 +1,344 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $context = $this->context; + $request = $this->request; + + // foo + if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + if (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + 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 (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + goto not_barhead; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + } + not_barhead: + + } + + if (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ($pathinfo === '/test/baz') { + return array('_route' => 'baz'); + } + + // baz2 + if ($pathinfo === '/test/baz.html') { + return array('_route' => 'baz2'); + } + + // baz3 + if (rtrim($pathinfo, '/') === '/test/baz3') { + 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 ($this->context->getMethod() != 'POST') { + $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 ($this->context->getMethod() != 'PUT') { + $allow[] = 'PUT'; + goto not_bazbaz6; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + } + not_bazbaz6: + + } + + // foofoo + if ($pathinfo === '/foofoo') { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + // quoter + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ($pathinfo === '/spa ce') { + 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 ()); + } + + } + + } + + if (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!',)); + } + + // overridden2 + if ($pathinfo === '/multi/new') { + return array('_route' => 'overridden2'); + } + + // hey + if (rtrim($pathinfo, '/') === '/multi/hey') { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'hey'); + } + + return array('_route' => 'hey'); + } + + } + + // 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 ($pathinfo === '/ababa') { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $this->context->getHost(); + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route1 + if ($pathinfo === '/route1') { + return array('_route' => 'route1'); + } + + // route2 + if ($pathinfo === '/c2/route2') { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) { + // route3 + if ($pathinfo === '/c2/route3') { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { + // route4 + if ($pathinfo === '/route4') { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) { + // route5 + if ($pathinfo === '/route5') { + return array('_route' => 'route5'); + } + + } + + // route6 + if ($pathinfo === '/route6') { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ($pathinfo === '/route11') { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ($pathinfo === '/route12') { + 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 ()); + } + + } + + if (0 === strpos($pathinfo, '/route1')) { + // 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 ($pathinfo === '/route17') { + return array('_route' => 'route17'); + } + + } + + if (0 === strpos($pathinfo, '/a')) { + // a + if ($pathinfo === '/a/a...') { + 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 ($pathinfo === '/secure') { + $requiredSchemes = array ( 'https' => 0,); + if (!isset($requiredSchemes[$this->context->getScheme()])) { + return $this->redirect($pathinfo, 'secure', key($requiredSchemes)); + } + + return array('_route' => 'secure'); + } + + // nonsecure + if ($pathinfo === '/nonsecure') { + $requiredSchemes = array ( 'http' => 0,); + if (!isset($requiredSchemes[$this->context->getScheme()])) { + 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..d9da7b02 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/dumper/url_matcher3.php @@ -0,0 +1,50 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + $context = $this->context; + $request = $this->request; + + if (0 === strpos($pathinfo, '/rootprefix')) { + // static + if ($pathinfo === '/rootprefix/test') { + 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 ($pathinfo === '/with-condition' && ($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/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/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..bdd6a473 --- /dev/null +++ b/vendor/symfony/routing/Tests/Fixtures/namespaceprefix.xml @@ -0,0 +1,13 @@ + + + + + + MyBundle:Blog:show + \w+ + en|fr|de + RouteCompiler + + 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/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..84337111 --- /dev/null +++ b/vendor/symfony/routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.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\Routing\Tests\Generator\Dumper; + +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 \PHPUnit_Framework_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($absoluteUrlWithParameter, 'http://localhost/app.php/testing/bar'); + $this->assertEquals($absoluteUrlWithoutParameter, 'http://localhost/app.php/testing2'); + $this->assertEquals($relativeUrlWithParameter, '/app.php/testing/bar'); + $this->assertEquals($relativeUrlWithoutParameter, '/app.php/testing2'); + } + + 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($absoluteUrlWithParameter, 'http://localhost/app.php/testing/bar'); + $this->assertEquals($absoluteUrlWithoutParameter, 'http://localhost/app.php/testing2'); + $this->assertEquals($relativeUrlWithParameter, '/app.php/testing/bar'); + $this->assertEquals($relativeUrlWithoutParameter, '/app.php/testing2'); + } + + /** + * @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($url, '/testing'); + } + + 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($absoluteUrl, 'ftp://localhost/app.php/testing'); + $this->assertEquals($relativeUrl, 'ftp://localhost/app.php/testing'); + + $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($absoluteUrl, 'https://localhost/app.php/testing'); + $this->assertEquals($relativeUrl, '/app.php/testing'); + } +} diff --git a/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php b/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php new file mode 100644 index 00000000..8b6c0d14 --- /dev/null +++ b/vendor/symfony/routing/Tests/Generator/UrlGeneratorTest.php @@ -0,0 +1,655 @@ + + * + * 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 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 \PHPUnit_Framework_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->getMock('Psr\Log\LoggerInterface'); + $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 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() + { + // 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('/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+%2B%2C%3B-._%7E%26%24%3C%3E%7C%7B%7D%25%5C%5E%60%21%3Ffoo%3Dbar%23id', + $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->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/', + ), + ); + } + + 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..288bf64f --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AbstractAnnotationLoaderTest.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\Tests\Loader; + +abstract class AbstractAnnotationLoaderTest extends \PHPUnit_Framework_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..83bf7108 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AnnotationClassLoaderTest.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\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' + ); + $defaults = array_replace($methodArgs, $routeData['defaults']); + $this->assertCount( + count($defaults), + array_intersect_assoc($defaults, $route->getDefaults()), + '->load preserves defaults annotation and merges them with default arguments in method signature' + ); + $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'); + } + + 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..29126ba4 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AnnotationDirectoryLoaderTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\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(2))->method('getClassAnnotation'); + + $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'); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.php new file mode 100644 index 00000000..a022af44 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/AnnotationFileLoaderTest.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\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'); + } + + /** + * @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..d34fa87e --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/ClosureLoaderTest.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\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\ClosureLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ClosureLoaderTest extends \PHPUnit_Framework_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..50b8d8ff --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/ObjectRouteLoaderTest.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\Loader; + +use Symfony\Component\Routing\Loader\ObjectRouteLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ObjectRouteLoaderTest extends \PHPUnit_Framework_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..5d66446f --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/PhpFileLoaderTest.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\Routing\Tests\Loader; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\PhpFileLoader; + +class PhpFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + public function testSupports() + { + $loader = new PhpFileLoader($this->getMock('Symfony\Component\Config\FileLocator')); + + $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..048d3ae9 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/XmlFileLoaderTest.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\Routing\Tests\Loader; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Routing\Tests\Fixtures\CustomXmlFileLoader; + +class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + public function testSupports() + { + $loader = new XmlFileLoader($this->getMock('Symfony\Component\Config\FileLocator')); + + $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')); + } + + 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')); + } +} diff --git a/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php b/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.php new file mode 100644 index 00000000..a6ae50b1 --- /dev/null +++ b/vendor/symfony/routing/Tests/Loader/YamlFileLoaderTest.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\Routing\Tests\Loader; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + public function testSupports() + { + $loader = new YamlFileLoader($this->getMock('Symfony\Component\Config\FileLocator')); + + $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..7b6001c1 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/Dumper/DumperCollectionTest.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\Matcher\Dumper; + +use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; + +class DumperCollectionTest extends \PHPUnit_Framework_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/DumperPrefixCollectionTest.php b/vendor/symfony/routing/Tests/Matcher/Dumper/DumperPrefixCollectionTest.php new file mode 100644 index 00000000..de01a75d --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/Dumper/DumperPrefixCollectionTest.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\Matcher\Dumper; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Matcher\Dumper\DumperPrefixCollection; +use Symfony\Component\Routing\Matcher\Dumper\DumperRoute; +use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; + +class DumperPrefixCollectionTest extends \PHPUnit_Framework_TestCase +{ + public function testAddPrefixRoute() + { + $coll = new DumperPrefixCollection(); + $coll->setPrefix(''); + + $route = new DumperRoute('bar', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar2', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('qux', new Route('/foo/qux')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar3', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar4', new Route('')); + $result = $coll->addPrefixRoute($route); + + $expect = <<<'EOF' + |-coll / + | |-coll /f + | | |-coll /fo + | | | |-coll /foo + | | | | |-coll /foo/ + | | | | | |-coll /foo/b + | | | | | | |-coll /foo/ba + | | | | | | | |-coll /foo/bar + | | | | | | | | |-route bar /foo/bar + | | | | | | | | |-route bar2 /foo/bar + | | | | | |-coll /foo/q + | | | | | | |-coll /foo/qu + | | | | | | | |-coll /foo/qux + | | | | | | | | |-route qux /foo/qux + | | | | | |-coll /foo/b + | | | | | | |-coll /foo/ba + | | | | | | | |-coll /foo/bar + | | | | | | | | |-route bar3 /foo/bar + | |-route bar4 / + +EOF; + + $this->assertSame($expect, $this->collectionToString($result->getRoot(), ' ')); + } + + public function testMergeSlashNodes() + { + $coll = new DumperPrefixCollection(); + $coll->setPrefix(''); + + $route = new DumperRoute('bar', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar2', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('qux', new Route('/foo/qux')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar3', new Route('/foo/bar')); + $result = $coll->addPrefixRoute($route); + + $result->getRoot()->mergeSlashNodes(); + + $expect = <<<'EOF' + |-coll /f + | |-coll /fo + | | |-coll /foo + | | | |-coll /foo/b + | | | | |-coll /foo/ba + | | | | | |-coll /foo/bar + | | | | | | |-route bar /foo/bar + | | | | | | |-route bar2 /foo/bar + | | | |-coll /foo/q + | | | | |-coll /foo/qu + | | | | | |-coll /foo/qux + | | | | | | |-route qux /foo/qux + | | | |-coll /foo/b + | | | | |-coll /foo/ba + | | | | | |-coll /foo/bar + | | | | | | |-route bar3 /foo/bar + +EOF; + + $this->assertSame($expect, $this->collectionToString($result->getRoot(), ' ')); + } + + private function collectionToString(DumperCollection $collection, $prefix) + { + $string = ''; + foreach ($collection as $route) { + if ($route instanceof DumperCollection) { + $string .= sprintf("%s|-coll %s\n", $prefix, $route->getPrefix()); + $string .= $this->collectionToString($route, $prefix.'| '); + } else { + $string .= sprintf("%s|-route %s %s\n", $prefix, $route->getName(), $route->getRoute()->getPath()); + } + } + + return $string; + } +} 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..0b9cdc23 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.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\Routing\Tests\Matcher\Dumper; + +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class PhpMatcherDumperTest extends \PHPUnit_Framework_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); + + 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()), + ); + } +} diff --git a/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.php new file mode 100644 index 00000000..b6c5a3e6 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/RedirectableUrlMatcherTest.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\Routing\Tests\Matcher; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class RedirectableUrlMatcherTest extends \PHPUnit_Framework_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..e43cbcb6 --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/TraceableUrlMatcherTest.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\Routing\Tests\Matcher; + +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 \PHPUnit_Framework_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..bf82fb7f --- /dev/null +++ b/vendor/symfony/routing/Tests/Matcher/UrlMatcherTest.php @@ -0,0 +1,419 @@ + + * + * 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 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 \PHPUnit_Framework_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).']+'))); + + $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->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->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->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..58612683 --- /dev/null +++ b/vendor/symfony/routing/Tests/RequestContextTest.php @@ -0,0 +1,159 @@ + + * + * 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 Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\RequestContext; + +class RequestContextTest extends \PHPUnit_Framework_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..b944ffe8 --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteCollectionBuilderTest.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\Routing\Tests; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouteCollectionBuilder; + +class RouteCollectionBuilderTest extends \PHPUnit_Framework_TestCase +{ + public function testImport() + { + $resolvedLoader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $resolver = $this->getMock('Symfony\Component\Config\Loader\LoaderResolverInterface'); + $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->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $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->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + // 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->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $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..376b8db7 --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteCollectionTest.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; + +class RouteCollectionTest extends \PHPUnit_Framework_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..b4b4f45a --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteCompilerTest.php @@ -0,0 +1,267 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use Symfony\Component\Routing\Route; + +class RouteCompilerTest extends \PHPUnit_Framework_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'), + ), + ), + ); + } + + /** + * @expectedException \LogicException + */ + public function testRouteWithSameVariableTwice() + { + $route = new Route('/{name}/{name}'); + + $compiled = $route->compile(); + } + + /** + * @dataProvider getNumericVariableNames + * @expectedException \DomainException + */ + public function testRouteWithNumericVariableName($name) + { + $route = new Route('/{'.$name.'}'); + $route->compile(); + } + + public function getNumericVariableNames() + { + 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'), + ), + ), + ); + } +} diff --git a/vendor/symfony/routing/Tests/RouteTest.php b/vendor/symfony/routing/Tests/RouteTest.php new file mode 100644 index 00000000..dc8e4fa2 --- /dev/null +++ b/vendor/symfony/routing/Tests/RouteTest.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 Symfony\Component\Routing\Tests; + +use Symfony\Component\Routing\Route; + +class RouteTest extends \PHPUnit_Framework_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..9a1c568e --- /dev/null +++ b/vendor/symfony/routing/Tests/RouterTest.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\Tests; + +use Symfony\Component\Routing\Router; +use Symfony\Component\HttpFoundation\Request; + +class RouterTest extends \PHPUnit_Framework_TestCase +{ + private $router = null; + + private $loader = null; + + protected function setUp() + { + $this->loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $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->getMock('Symfony\Component\Routing\RouteCollection'); + + $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->getMock('Symfony\Component\Routing\RouteCollection'))); + + $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->getMock('Symfony\Component\Routing\RouteCollection'))); + + $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->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); + $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->getMock('Symfony\Component\Routing\Matcher\RequestMatcherInterface'); + $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..2d22806e --- /dev/null +++ b/vendor/symfony/routing/composer.json @@ -0,0 +1,53 @@ +{ + "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": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "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.0-dev" + } + } +} diff --git a/vendor/symfony/routing/phpunit.xml.dist b/vendor/symfony/routing/phpunit.xml.dist new file mode 100644 index 00000000..b69f066a --- /dev/null +++ b/vendor/symfony/routing/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/web/.htaccess b/web/.htaccess new file mode 100644 index 00000000..209500b7 --- /dev/null +++ b/web/.htaccess @@ -0,0 +1,11 @@ + + Options -MultiViews + + RewriteEngine On + #RewriteBase /path/to/app + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [QSA,L] + RewriteCond %{HTTP:Authorization} ^(.*) + RewriteRule .* - [e=HTTP_AUTHORIZATION:%1] + \ No newline at end of file diff --git a/web/index.php b/web/index.php new file mode 100644 index 00000000..3a1d270b --- /dev/null +++ b/web/index.php @@ -0,0 +1,120 @@ +getMessage(); +} + +$app->post('/auth', function (Request $request) use ($app) { + $data = json_decode($request->getContent(), true); + + if($data['user'] == 'maicon' && $data['pass'] == '123456') { + $jwt = JWTWrapper::encode([ + 'expiration_sec' => 3600, + 'iss' => 'maiconprange', + 'userdata' => [ + 'id' => 1, + 'name' => 'Maicon Eduardo Prange' + ] + ]); + + return $app->json([ + 'login' => 'true', + 'access_token' => $jwt + ]); + } + + return $app->json([ + 'login' => 'false', + 'message' => 'Invalid login', + ]); +}); + +$app->before(function(Request $request, Application $app) { + $route = $request->get('_route'); + + if($route != 'POST_auth') { + $authorization = $request->headers->get("Authorization"); + list($jwt) = sscanf($authorization, 'Bearer %s'); + + if($jwt) { + try { + $app['jwt'] = JWTWrapper::decode($jwt); + } catch(Exception $ex) { + return new Response('Unauthorized access', 400); + } + + } else { + return new Response('Token not informed', 400); + } + } +}); + +$app->get('/contact', function () use ($app, $dbh) { + $sth = $dbh->prepare('SELECT id, first_name, last_name, email, phone_number FROM list_contacts'); + $sth->execute(); + $contatos = $sth->fetchAll(PDO::FETCH_ASSOC); + + return $app->json($contatos); +}); + +$app->get('/contact/{id}', function ($id) use ($app, $dbh) { + $sth = $dbh->prepare('SELECT * FROM list_contacts WHERE id=?'); + $sth->execute([$id]); + $contato = $sth->fetchAll(PDO::FETCH_ASSOC); + if(empty($contato)) { + return new Response("Contact id {$id} not found!", 404); + } + + return $app->json($contato); +})->assert('id', '\d+'); + +$app->post('/contact', function(Request $request) use ($app, $dbh) { + $data = json_decode($request->getContent(), true); + + $sth = $dbh->prepare('INSERT INTO list_contacts (first_name, last_name, email, phone_number) + VALUES(:first_name, :last_name, :email, :phone_number)'); + + $sth->execute($data); + $id = $dbh->lastInsertId(); + + $response = new Response('Inserted correctly', 201); + $response->headers->set('Location', "/list_contacts/$id"); + return $response; +}); + +$app->put('/contact/{id}', function(Request $request, $id) use ($app, $dbh) { + $data = json_decode($request->getContent(), true); + $data['id'] = $id; + + $sth = $dbh->prepare('UPDATE list_contacts + SET first_name=:first_name, last_name=:last_name, email=:email, phone_number:phone_number + WHERE id=:id'); + + $sth->execute($data); + return $app->json($data, 200); +})->assert('id', '\d+'); + +$app->delete('/contact/{id}', function($id) use ($app, $dbh) { + $sth = $dbh->prepare('DELETE FROM list_contacts WHERE id = ?'); + $sth->execute([ $id ]); + + if($sth->rowCount() < 1) { + return new Response("Contact id {$id} not found!", 404); + } + + return new Response(null, 204); +})->assert('id', '\d+'); + +$app->run(); \ No newline at end of file From 3b53da7562d32d6478ee779c29cb66acf5c41767 Mon Sep 17 00:00:00 2001 From: "maicon.senior" Date: Fri, 30 Jun 2017 13:54:31 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20do=20banco=20de=20d?= =?UTF-8?q?ados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mysql/contacts.sql | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 mysql/contacts.sql diff --git a/mysql/contacts.sql b/mysql/contacts.sql new file mode 100644 index 00000000..3f9c6983 --- /dev/null +++ b/mysql/contacts.sql @@ -0,0 +1,69 @@ +-- phpMyAdmin SQL Dump +-- version 4.3.11 +-- http://www.phpmyadmin.net +-- +-- Host: 127.0.0.1 +-- Generation Time: 30-Jun-2017 às 17:03 +-- Versão do servidor: 5.6.24 +-- PHP Version: 5.6.8 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + +-- +-- Database: `contacts` +-- + +-- -------------------------------------------------------- + +-- +-- Estrutura da tabela `list_contacts` +-- + +CREATE TABLE IF NOT EXISTS `list_contacts` ( + `id` int(11) NOT NULL, + `first_name` varchar(255) NOT NULL, + `last_name` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, + `phone_number` varchar(255) NOT NULL +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1; + +-- +-- Extraindo dados da tabela `list_contacts` +-- + +INSERT INTO `list_contacts` (`id`, `first_name`, `last_name`, `email`, `phone_number`) VALUES +(1, 'Maicon Eduardo', 'Prange', 'maiconprange@hotmail.com.br', '47984289831'), +(2, 'Fernanda da Sila', 'Prange', 'fernanda@hotmail.com.br', '47984289832'), +(3, 'Amanda', 'Prange', 'amanda@hotmail.com.br', '47984289833'), +(4, 'Teste', 'Testando', 'teste@teste.com.br', '8987987897897'), +(5, 'Teste', 'Testando', 'teste@teste.com.br', '8987987897897'); + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `list_contacts` +-- +ALTER TABLE `list_contacts` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `list_contacts` +-- +ALTER TABLE `list_contacts` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=6; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
  • @eR}@2I=H|i^_}qC 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�{W!A9%P>K|0?%)h1 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 F_lUv4zu#46g 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 z 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 b=l}a}e_x6J`CtCeAOF*De)AiiQJDV^93d3o literal 0 HcmV?d00001 diff --git a/src/JWTWrapper.php b/src/JWTWrapper.php new file mode 100644 index 00000000..76d696cc --- /dev/null +++ b/src/JWTWrapper.php @@ -0,0 +1,28 @@ + $issuedAt, + 'iss' => $options['iss'], + 'exp' => $expire, + 'nbf' => $issuedAt - 1, + 'data' => $options['userdata'], + ]; + + return JWT::encode($tokenParam, self::KEY); + } + + public static function decode($jwt) + { + return JWT::decode($jwt, self::KEY, ['HS256']); + } +} \ No newline at end of file diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 00000000..81eba14d --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + foreach ($this->prefixDirsPsr4[$search] as $dir) { + $length = $this->prefixLengthsPsr4[$first][$search]; + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 00000000..f27399a0 --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 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..02a5df50 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,10 @@ + array($vendorDir . '/pimple/pimple/lib'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 00000000..b3f327fb --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,18 @@ + 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'), + 'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 00000000..56565c60 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit160caefd790170adcf64c3c5ab467caf::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\ComposerStaticInit160caefd790170adcf64c3c5ab467caf::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire160caefd790170adcf64c3c5ab467caf($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire160caefd790170adcf64c3c5ab467caf($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..dd60ec91 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,92 @@ + __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, + ), + 'F' => + array ( + 'Firebase\\JWT\\' => 13, + ), + ); + + 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', + ), + 'Firebase\\JWT\\' => + array ( + 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', + ), + ); + + public static $prefixesPsr0 = array ( + 'P' => + array ( + 'Pimple' => + array ( + 0 => __DIR__ . '/..' . '/pimple/pimple/lib', + ), + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit160caefd790170adcf64c3c5ab467caf::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit160caefd790170adcf64c3c5ab467caf::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit160caefd790170adcf64c3c5ab467caf::$prefixesPsr0; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 00000000..190046bf --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,623 @@ +[ + { + "name": "symfony/routing", + "version": "v3.0.9", + "version_normalized": "3.0.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "9038984bd9c05ab07280121e9e10f61a7231457b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/9038984bd9c05ab07280121e9e10f61a7231457b", + "reference": "9038984bd9c05ab07280121e9e10f61a7231457b", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/http-foundation": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "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": "2016-06-29T05:40:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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": "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-10T12:19:37+00:00", + "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-01T21:01:25+00:00", + "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/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-09T14:24:12+00:00", + "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.0.9", + "version_normalized": "3.0.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "49ba00f8ede742169cb6b70abe33243f4d673f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49ba00f8ede742169cb6b70abe33243f4d673f82", + "reference": "49ba00f8ede742169cb6b70abe33243f4d673f82", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0" + }, + "time": "2016-07-17T13:54:30+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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.0.9", + "version_normalized": "3.0.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "54da3ff63dec3c9c0e32ec3f95a7d94ef64baa00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/54da3ff63dec3c9c0e32ec3f95a7d94ef64baa00", + "reference": "54da3ff63dec3c9c0e32ec3f95a7d94ef64baa00", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2016-07-19T10:44:15+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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": "symfony/http-kernel", + "version": "v3.0.9", + "version_normalized": "3.0.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "d97ba4425e36e79c794e7d14ff36f00f081b37b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d97ba4425e36e79c794e7d14ff36f00f081b37b3", + "reference": "d97ba4425e36e79c794e7d14ff36f00f081b37b3", + "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": "~2.8.8|~3.0.8|~3.1.2|~3.2" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "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": "~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/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": "~2.8|~3.0" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "time": "2016-07-30T09:10:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-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": "v1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d", + "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2013-11-22T08:30:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Pimple": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ] + }, + { + "name": "silex/silex", + "version": "v1.3.6", + "version_normalized": "1.3.6.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex.git", + "reference": "ff8aa6bc2e066e14b07e0c63e9bd9dd1458af136" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex/zipball/ff8aa6bc2e066e14b07e0c63e9bd9dd1458af136", + "reference": "ff8aa6bc2e066e14b07e0c63e9bd9dd1458af136", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "pimple/pimple": "~1.0", + "symfony/event-dispatcher": "~2.3|3.0.*", + "symfony/http-foundation": "~2.3|3.0.*", + "symfony/http-kernel": "~2.3|3.0.*", + "symfony/routing": "~2.3|3.0.*" + }, + "require-dev": { + "doctrine/dbal": "~2.2", + "monolog/monolog": "^1.4.1", + "swiftmailer/swiftmailer": "~5", + "symfony/browser-kit": "~2.3|3.0.*", + "symfony/config": "~2.3|3.0.*", + "symfony/css-selector": "~2.3|3.0.*", + "symfony/debug": "~2.3|3.0.*", + "symfony/dom-crawler": "~2.3|3.0.*", + "symfony/finder": "~2.3|3.0.*", + "symfony/form": "~2.3|3.0.*", + "symfony/intl": "~2.3|3.0.*", + "symfony/monolog-bridge": "~2.3|3.0.*", + "symfony/options-resolver": "~2.3|3.0.*", + "symfony/phpunit-bridge": "~2.7", + "symfony/process": "~2.3|3.0.*", + "symfony/security": "~2.3|3.0.*", + "symfony/serializer": "~2.3|3.0.*", + "symfony/translation": "~2.3|3.0.*", + "symfony/twig-bridge": "~2.3|3.0.*", + "symfony/validator": "~2.3|3.0.*", + "twig/twig": "~1.28|~2.0" + }, + "time": "2017-04-30T16:26:54+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.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" + ] + }, + { + "name": "firebase/php-jwt", + "version": "v5.0.0", + "version_normalized": "5.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": " 4.8.35" + }, + "time": "2017-06-27T22:17:23+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt" + } +] diff --git a/vendor/firebase/php-jwt/LICENSE b/vendor/firebase/php-jwt/LICENSE new file mode 100644 index 00000000..cb0c49b3 --- /dev/null +++ b/vendor/firebase/php-jwt/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2011, Neuman Vong + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Neuman Vong nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/firebase/php-jwt/README.md b/vendor/firebase/php-jwt/README.md new file mode 100644 index 00000000..b1a7a3a2 --- /dev/null +++ b/vendor/firebase/php-jwt/README.md @@ -0,0 +1,200 @@ +[![Build Status](https://travis-ci.org/firebase/php-jwt.png?branch=master)](https://travis-ci.org/firebase/php-jwt) +[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt) +[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt) +[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt) + +PHP-JWT +======= +A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519). + +Installation +------------ + +Use composer to manage your dependencies and download PHP-JWT: + +```bash +composer require firebase/php-jwt +``` + +Example +------- +```php + "http://example.org", + "aud" => "http://example.com", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +/** + * IMPORTANT: + * You must specify supported algorithms for your application. See + * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 + * for a list of spec-compliant algorithms. + */ +$jwt = JWT::encode($token, $key); +$decoded = JWT::decode($jwt, $key, array('HS256')); + +print_r($decoded); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; + +/** + * You can add a leeway to account for when there is a clock skew times between + * the signing and verifying servers. It is recommended that this leeway should + * not be bigger than a few minutes. + * + * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef + */ +JWT::$leeway = 60; // $leeway in seconds +$decoded = JWT::decode($jwt, $key, array('HS256')); + +?> +``` +Example with RS256 (openssl) +---------------------------- +```php + "example.org", + "aud" => "example.com", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +$jwt = JWT::encode($token, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, $publicKey, array('RS256')); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; +echo "Decode:\n" . print_r($decoded_array, true) . "\n"; +?> +``` + +Changelog +--------- + +#### 5.0.0 / 2017-06-26 +- Support RS384 and RS512. + See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)! +- Add an example for RS256 openssl. + See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)! +- Detect invalid Base64 encoding in signature. + See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)! +- Update `JWT::verify` to handle OpenSSL errors. + See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)! +- Add `array` type hinting to `decode` method + See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)! +- Add all JSON error types. + See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)! +- Bugfix 'kid' not in given key list. + See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)! +- Miscellaneous cleanup, documentation and test fixes. + See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115), + [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and + [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman), + [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)! + +#### 4.0.0 / 2016-07-17 +- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! +- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! +- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! +- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! + +#### 3.0.0 / 2015-07-22 +- Minimum PHP version updated from `5.2.0` to `5.3.0`. +- Add `\Firebase\JWT` namespace. See +[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to +[@Dashron](https://github.com/Dashron)! +- Require a non-empty key to decode and verify a JWT. See +[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to +[@sjones608](https://github.com/sjones608)! +- Cleaner documentation blocks in the code. See +[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to +[@johanderuijter](https://github.com/johanderuijter)! + +#### 2.2.0 / 2015-06-22 +- Add support for adding custom, optional JWT headers to `JWT::encode()`. See +[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to +[@mcocaro](https://github.com/mcocaro)! + +#### 2.1.0 / 2015-05-20 +- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew +between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! +- Add support for passing an object implementing the `ArrayAccess` interface for +`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! + +#### 2.0.0 / 2015-04-01 +- **Note**: It is strongly recommended that you update to > v2.0.0 to address + known security vulnerabilities in prior versions when both symmetric and + asymmetric keys are used together. +- Update signature for `JWT::decode(...)` to require an array of supported + algorithms to use when verifying token signatures. + + +Tests +----- +Run the tests using phpunit: + +```bash +$ pear install PHPUnit +$ phpunit --configuration phpunit.xml.dist +PHPUnit 3.7.10 by Sebastian Bergmann. +..... +Time: 0 seconds, Memory: 2.50Mb +OK (5 tests, 5 assertions) +``` + +New Lines in private keys +----- + +If your private key contains `\n` characters, be sure to wrap it in double quotes `""` +and not single quotes `''` in order to properly interpret the escaped characters. + +License +------- +[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause). diff --git a/vendor/firebase/php-jwt/composer.json b/vendor/firebase/php-jwt/composer.json new file mode 100644 index 00000000..b76ffd19 --- /dev/null +++ b/vendor/firebase/php-jwt/composer.json @@ -0,0 +1,29 @@ +{ + "name": "firebase/php-jwt", + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "license": "BSD-3-Clause", + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "require-dev": { + "phpunit/phpunit": " 4.8.35" + } +} diff --git a/vendor/firebase/php-jwt/src/BeforeValidException.php b/vendor/firebase/php-jwt/src/BeforeValidException.php new file mode 100644 index 00000000..a6ee2f7c --- /dev/null +++ b/vendor/firebase/php-jwt/src/BeforeValidException.php @@ -0,0 +1,7 @@ + + * @author Anant Narayanan + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWT +{ + + /** + * When checking nbf, iat or expiration times, + * we want to provide some extra leeway time to + * account for clock skew. + */ + public static $leeway = 0; + + /** + * Allow the current timestamp to be specified. + * Useful for fixing a value within unit testing. + * + * Will default to PHP time() value if null. + */ + public static $timestamp = null; + + public static $supported_algs = array( + 'HS256' => array('hash_hmac', 'SHA256'), + 'HS512' => array('hash_hmac', 'SHA512'), + 'HS384' => array('hash_hmac', 'SHA384'), + 'RS256' => array('openssl', 'SHA256'), + 'RS384' => array('openssl', 'SHA384'), + 'RS512' => array('openssl', 'SHA512'), + ); + + /** + * Decodes a JWT string into a PHP object. + * + * @param string $jwt The JWT + * @param string|array $key The key, or map of keys. + * If the algorithm used is asymmetric, this is the public key + * @param array $allowed_algs List of supported verification algorithms + * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' + * + * @return object The JWT's payload as a PHP object + * + * @throws UnexpectedValueException Provided JWT was invalid + * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed + * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' + * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' + * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim + * + * @uses jsonDecode + * @uses urlsafeB64Decode + */ + public static function decode($jwt, $key, array $allowed_algs = array()) + { + $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp; + + if (empty($key)) { + throw new InvalidArgumentException('Key may not be empty'); + } + $tks = explode('.', $jwt); + if (count($tks) != 3) { + throw new UnexpectedValueException('Wrong number of segments'); + } + list($headb64, $bodyb64, $cryptob64) = $tks; + if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) { + throw new UnexpectedValueException('Invalid header encoding'); + } + if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) { + throw new UnexpectedValueException('Invalid claims encoding'); + } + if (false === ($sig = static::urlsafeB64Decode($cryptob64))) { + throw new UnexpectedValueException('Invalid signature encoding'); + } + if (empty($header->alg)) { + throw new UnexpectedValueException('Empty algorithm'); + } + if (empty(static::$supported_algs[$header->alg])) { + throw new UnexpectedValueException('Algorithm not supported'); + } + if (!in_array($header->alg, $allowed_algs)) { + throw new UnexpectedValueException('Algorithm not allowed'); + } + if (is_array($key) || $key instanceof \ArrayAccess) { + if (isset($header->kid)) { + if (!isset($key[$header->kid])) { + throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + } + $key = $key[$header->kid]; + } else { + throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); + } + } + + // Check the signature + if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) { + throw new SignatureInvalidException('Signature verification failed'); + } + + // Check if the nbf if it is defined. This is the time that the + // token can actually be used. If it's not yet that time, abort. + if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf) + ); + } + + // Check that this token has been created before 'now'. This prevents + // using tokens that have been created for later use (and haven't + // correctly used the nbf claim). + if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat) + ); + } + + // Check if this token has expired. + if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + throw new ExpiredException('Expired token'); + } + + return $payload; + } + + /** + * Converts and signs a PHP object or array into a JWT string. + * + * @param object|array $payload PHP object or array + * @param string $key The secret key. + * If the algorithm used is asymmetric, this is the private key + * @param string $alg The signing algorithm. + * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' + * @param mixed $keyId + * @param array $head An array with header elements to attach + * + * @return string A signed JWT + * + * @uses jsonEncode + * @uses urlsafeB64Encode + */ + public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null) + { + $header = array('typ' => 'JWT', 'alg' => $alg); + if ($keyId !== null) { + $header['kid'] = $keyId; + } + if ( isset($head) && is_array($head) ) { + $header = array_merge($head, $header); + } + $segments = array(); + $segments[] = static::urlsafeB64Encode(static::jsonEncode($header)); + $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload)); + $signing_input = implode('.', $segments); + + $signature = static::sign($signing_input, $key, $alg); + $segments[] = static::urlsafeB64Encode($signature); + + return implode('.', $segments); + } + + /** + * Sign a string with a given key and algorithm. + * + * @param string $msg The message to sign + * @param string|resource $key The secret key + * @param string $alg The signing algorithm. + * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' + * + * @return string An encrypted message + * + * @throws DomainException Unsupported algorithm was specified + */ + public static function sign($msg, $key, $alg = 'HS256') + { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + list($function, $algorithm) = static::$supported_algs[$alg]; + switch($function) { + case 'hash_hmac': + return hash_hmac($algorithm, $msg, $key, true); + case 'openssl': + $signature = ''; + $success = openssl_sign($msg, $signature, $key, $algorithm); + if (!$success) { + throw new DomainException("OpenSSL unable to sign data"); + } else { + return $signature; + } + } + } + + /** + * Verify a signature with the message, key and method. Not all methods + * are symmetric, so we must have a separate verify and sign method. + * + * @param string $msg The original message (header and body) + * @param string $signature The original signature + * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key + * @param string $alg The algorithm + * + * @return bool + * + * @throws DomainException Invalid Algorithm or OpenSSL failure + */ + private static function verify($msg, $signature, $key, $alg) + { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + + list($function, $algorithm) = static::$supported_algs[$alg]; + switch($function) { + case 'openssl': + $success = openssl_verify($msg, $signature, $key, $algorithm); + if ($success === 1) { + return true; + } elseif ($success === 0) { + return false; + } + // returns 1 on success, 0 on failure, -1 on error. + throw new DomainException( + 'OpenSSL error: ' . openssl_error_string() + ); + case 'hash_hmac': + default: + $hash = hash_hmac($algorithm, $msg, $key, true); + if (function_exists('hash_equals')) { + return hash_equals($signature, $hash); + } + $len = min(static::safeStrlen($signature), static::safeStrlen($hash)); + + $status = 0; + for ($i = 0; $i < $len; $i++) { + $status |= (ord($signature[$i]) ^ ord($hash[$i])); + } + $status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash)); + + return ($status === 0); + } + } + + /** + * Decode a JSON string into a PHP object. + * + * @param string $input JSON string + * + * @return object Object representation of JSON string + * + * @throws DomainException Provided string was invalid JSON + */ + public static function jsonDecode($input) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) { + /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you + * to specify that large ints (like Steam Transaction IDs) should be treated as + * strings, rather than the PHP default behaviour of converting them to floats. + */ + $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING); + } else { + /** Not all servers will support that, however, so for older versions we must + * manually detect large ints in the JSON string and quote them (thus converting + *them to strings) before decoding, hence the preg_replace() call. + */ + $max_int_length = strlen((string) PHP_INT_MAX) - 1; + $json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input); + $obj = json_decode($json_without_bigints); + } + + if (function_exists('json_last_error') && $errno = json_last_error()) { + static::handleJsonError($errno); + } elseif ($obj === null && $input !== 'null') { + throw new DomainException('Null result with non-null input'); + } + return $obj; + } + + /** + * Encode a PHP object into a JSON string. + * + * @param object|array $input A PHP object or array + * + * @return string JSON representation of the PHP object or array + * + * @throws DomainException Provided object could not be encoded to valid JSON + */ + public static function jsonEncode($input) + { + $json = json_encode($input); + if (function_exists('json_last_error') && $errno = json_last_error()) { + static::handleJsonError($errno); + } elseif ($json === 'null' && $input !== null) { + throw new DomainException('Null result with non-null input'); + } + return $json; + } + + /** + * Decode a string with URL-safe Base64. + * + * @param string $input A Base64 encoded string + * + * @return string A decoded string + */ + public static function urlsafeB64Decode($input) + { + $remainder = strlen($input) % 4; + if ($remainder) { + $padlen = 4 - $remainder; + $input .= str_repeat('=', $padlen); + } + return base64_decode(strtr($input, '-_', '+/')); + } + + /** + * Encode a string with URL-safe Base64. + * + * @param string $input The string you want encoded + * + * @return string The base64 encode of what you passed in + */ + public static function urlsafeB64Encode($input) + { + return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); + } + + /** + * Helper method to create a JSON error. + * + * @param int $errno An error number from json_last_error() + * + * @return void + */ + private static function handleJsonError($errno) + { + $messages = array( + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3 + ); + throw new DomainException( + isset($messages[$errno]) + ? $messages[$errno] + : 'Unknown JSON error: ' . $errno + ); + } + + /** + * Get the number of bytes in cryptographic strings. + * + * @param string + * + * @return int + */ + private static function safeStrlen($str) + { + if (function_exists('mb_strlen')) { + return mb_strlen($str, '8bit'); + } + return strlen($str); + } +} diff --git a/vendor/firebase/php-jwt/src/SignatureInvalidException.php b/vendor/firebase/php-jwt/src/SignatureInvalidException.php new file mode 100644 index 00000000..27332b21 --- /dev/null +++ b/vendor/firebase/php-jwt/src/SignatureInvalidException.php @@ -0,0 +1,7 @@ +share(function ($c) { + return new Session($c['session_storage']); + }); + +Protecting Parameters +--------------------- + +Because Pimple sees anonymous functions as service definitions, you need to +wrap anonymous functions with the ``protect()`` method to store them as +parameter:: + + $container['random'] = $container->protect(function () { return rand(); }); + +Modifying services after creation +--------------------------------- + +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:: + + $container['mail'] = function ($c) { + return new \Zend_Mail(); + }; + + $container['mail'] = $container->extend('mail', function($mail, $c) { + $mail->setFrom($c['mail.default_from']); + return $mail; + }); + +The first argument is the name of the object, the second is a function that +gets access to the object instance and the container. The return value is +a service definition, so you need to re-assign it on the container. + +If the service you plan to extend is already shared, it's recommended that you +re-wrap your extended service with the ``shared`` method, otherwise your extension +code will be called every time you access the service:: + + $container['twig'] = $container->share(function ($c) { + return new Twig_Environment($c['twig.loader'], $c['twig.options']); + }); + + $container['twig'] = $container->share($container->extend('twig', function ($twig, $c) { + $twig->addExtension(new MyTwigExtension()); + return $twig; + })); + +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:: + + $container['session'] = $container->share(function ($c) { + return new Session($c['session_storage']); + }); + + $sessionFunction = $container->raw('session'); + +Packaging a Container for reusability +------------------------------------- + +If you use the same libraries over and over, you might want to create reusable +containers. Creating a reusable container is as simple as creating a class +that extends ``Pimple``, and configuring it in the constructor:: + + class SomeContainer extends Pimple + { + public function __construct() + { + $this['parameter'] = 'foo'; + $this['object'] = function () { return stdClass(); }; + } + } + +Using this container from your own is as easy as it can get:: + + $container = new Pimple(); + + // define your project parameters and services + // ... + + // embed the SomeContainer container + $container['embedded'] = $container->share(function () { return new SomeContainer(); }); + + // configure it + $container['embedded']['parameter'] = 'bar'; + + // use it + $container['embedded']['object']->...; + +.. _Download it: https://github.com/fabpot/Pimple diff --git a/vendor/pimple/pimple/composer.json b/vendor/pimple/pimple/composer.json new file mode 100644 index 00000000..d95c8c52 --- /dev/null +++ b/vendor/pimple/pimple/composer.json @@ -0,0 +1,25 @@ +{ + "name": "pimple/pimple", + "type": "library", + "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", + "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": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} \ No newline at end of file diff --git a/vendor/pimple/pimple/lib/Pimple.php b/vendor/pimple/pimple/lib/Pimple.php new file mode 100644 index 00000000..b980522d --- /dev/null +++ b/vendor/pimple/pimple/lib/Pimple.php @@ -0,0 +1,214 @@ +values = $values; + } + + /** + * 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 a 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 defined an object + */ + public function offsetSet($id, $value) + { + $this->values[$id] = $value; + } + + /** + * 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 (!array_key_exists($id, $this->values)) { + throw new InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id)); + } + + $isFactory = is_object($this->values[$id]) && method_exists($this->values[$id], '__invoke'); + + return $isFactory ? $this->values[$id]($this) : $this->values[$id]; + } + + /** + * Checks if a parameter or an object is set. + * + * @param string $id The unique identifier for the parameter or object + * + * @return Boolean + */ + public function offsetExists($id) + { + return array_key_exists($id, $this->values); + } + + /** + * Unsets a parameter or an object. + * + * @param string $id The unique identifier for the parameter or object + */ + public function offsetUnset($id) + { + unset($this->values[$id]); + } + + /** + * Returns a closure that stores the result of the given service definition + * for uniqueness in the scope of this instance of Pimple. + * + * @param callable $callable A service definition to wrap for uniqueness + * + * @return Closure The wrapped closure + */ + public static function share($callable) + { + if (!is_object($callable) || !method_exists($callable, '__invoke')) { + throw new InvalidArgumentException('Service definition is not a Closure or invokable object.'); + } + + return function ($c) use ($callable) { + static $object; + + if (null === $object) { + $object = $callable($c); + } + + return $object; + }; + } + + /** + * 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 Closure The protected closure + */ + public static function protect($callable) + { + if (!is_object($callable) || !method_exists($callable, '__invoke')) { + throw new InvalidArgumentException('Callable is not a Closure or invokable object.'); + } + + return function ($c) use ($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 (!array_key_exists($id, $this->values)) { + throw new InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $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 Closure The wrapped closure + * + * @throws InvalidArgumentException if the identifier is not defined or not a service definition + */ + public function extend($id, $callable) + { + if (!array_key_exists($id, $this->values)) { + 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]; + + return $this->values[$id] = function ($c) use ($callable, $factory) { + return $callable($factory($c), $c); + }; + } + + /** + * Returns all defined value names. + * + * @return array An array of value names + */ + public function keys() + { + return array_keys($this->values); + } +} diff --git a/vendor/pimple/pimple/phpunit.xml.dist b/vendor/pimple/pimple/phpunit.xml.dist new file mode 100644 index 00000000..9de2c19e --- /dev/null +++ b/vendor/pimple/pimple/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + ./tests/Pimple/ + + + diff --git a/vendor/pimple/pimple/tests/Pimple/Tests/Invokable.php b/vendor/pimple/pimple/tests/Pimple/Tests/Invokable.php new file mode 100644 index 00000000..aff09e99 --- /dev/null +++ b/vendor/pimple/pimple/tests/Pimple/Tests/Invokable.php @@ -0,0 +1,38 @@ +value = $value; + + return $service; + } +} diff --git a/vendor/pimple/pimple/tests/Pimple/Tests/NonInvokable.php b/vendor/pimple/pimple/tests/Pimple/Tests/NonInvokable.php new file mode 100644 index 00000000..c9e4c25f --- /dev/null +++ b/vendor/pimple/pimple/tests/Pimple/Tests/NonInvokable.php @@ -0,0 +1,34 @@ + + */ +class PimpleTest extends \PHPUnit_Framework_TestCase +{ + public function testWithString() + { + $pimple = new Pimple(); + $pimple['param'] = 'value'; + + $this->assertEquals('value', $pimple['param']); + } + + public function testWithClosure() + { + $pimple = new Pimple(); + $pimple['service'] = function () { + return new Service(); + }; + + $this->assertInstanceOf('Pimple\Tests\Service', $pimple['service']); + } + + public function testServicesShouldBeDifferent() + { + $pimple = new Pimple(); + $pimple['service'] = function () { + return new Service(); + }; + + $serviceOne = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Service', $serviceOne); + + $serviceTwo = $pimple['service']; + $this->assertInstanceOf('Pimple\Tests\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + } + + public function testShouldPassContainerAsParameter() + { + $pimple = new Pimple(); + $pimple['service'] = function () { + return new Service(); + }; + $pimple['container'] = function ($container) { + return $container; + }; + + $this->assertNotSame($pimple, $pimple['service']); + $this->assertSame($pimple, $pimple['container']); + } + + public function testIsset() + { + $pimple = new Pimple(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new 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 Pimple($params); + + $this->assertSame($params['param'], $pimple['param']); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testOffsetGetValidatesKeyIsPresent() + { + $pimple = new Pimple(); + echo $pimple['foo']; + } + + public function testOffsetGetHonorsNullValues() + { + $pimple = new Pimple(); + $pimple['foo'] = null; + $this->assertNull($pimple['foo']); + } + + public function testUnset() + { + $pimple = new Pimple(); + $pimple['param'] = 'value'; + $pimple['service'] = function () { + return new Service(); + }; + + unset($pimple['param'], $pimple['service']); + $this->assertFalse(isset($pimple['param'])); + $this->assertFalse(isset($pimple['service'])); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testShare($service) + { + $pimple = new Pimple(); + $pimple['shared_service'] = $pimple->share($service); + + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Service', $serviceOne); + + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Service', $serviceTwo); + + $this->assertSame($serviceOne, $serviceTwo); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testProtect($service) + { + $pimple = new Pimple(); + $pimple['protected'] = $pimple->protect($service); + + $this->assertSame($service, $pimple['protected']); + } + + public function testGlobalFunctionNameAsParameterValue() + { + $pimple = new Pimple(); + $pimple['global_function'] = 'strlen'; + $this->assertSame('strlen', $pimple['global_function']); + } + + public function testRaw() + { + $pimple = new Pimple(); + $pimple['service'] = $definition = function () { return 'foo'; }; + $this->assertSame($definition, $pimple->raw('service')); + } + + public function testRawHonorsNullValues() + { + $pimple = new Pimple(); + $pimple['foo'] = null; + $this->assertNull($pimple->raw('foo')); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testRawValidatesKeyIsPresent() + { + $pimple = new Pimple(); + $pimple->raw('foo'); + } + + /** + * @dataProvider serviceDefinitionProvider + */ + public function testExtend($service) + { + $pimple = new Pimple(); + $pimple['shared_service'] = $pimple->share(function () { + return new Service(); + }); + + $pimple->extend('shared_service', $service); + + $serviceOne = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Service', $serviceOne); + + $serviceTwo = $pimple['shared_service']; + $this->assertInstanceOf('Pimple\Tests\Service', $serviceTwo); + + $this->assertNotSame($serviceOne, $serviceTwo); + $this->assertSame($serviceOne->value, $serviceTwo->value); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" is not defined. + */ + public function testExtendValidatesKeyIsPresent() + { + $pimple = new Pimple(); + $pimple->extend('foo', function () {}); + } + + public function testKeys() + { + $pimple = new Pimple(); + $pimple['foo'] = 123; + $pimple['bar'] = 123; + + $this->assertEquals(array('foo', 'bar'), $pimple->keys()); + } + + /** @test */ + public function settingAnInvokableObjectShouldTreatItAsFactory() + { + $pimple = new Pimple(); + $pimple['invokable'] = new Invokable(); + + $this->assertInstanceOf('Pimple\Tests\Service', $pimple['invokable']); + } + + /** @test */ + public function settingNonInvokableObjectShouldTreatItAsParameter() + { + $pimple = new Pimple(); + $pimple['non_invokable'] = new NonInvokable(); + + $this->assertInstanceOf('Pimple\Tests\NonInvokable', $pimple['non_invokable']); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Service definition is not a Closure or invokable object. + */ + public function testShareFailsForInvalidServiceDefinitions($service) + { + $pimple = new Pimple(); + $pimple->share($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Callable is not a Closure or invokable object. + */ + public function testProtectFailsForInvalidServiceDefinitions($service) + { + $pimple = new Pimple(); + $pimple->protect($service); + } + + /** + * @dataProvider badServiceDefinitionProvider + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Identifier "foo" does not contain an object definition. + */ + public function testExtendFailsForKeysNotContainingServiceDefinitions($service) + { + $pimple = new Pimple(); + $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 Pimple(); + $pimple['foo'] = function () {}; + $pimple->extend('foo', $service); + } + + /** + * Provider for invalid service definitions + */ + public function badServiceDefinitionProvider() + { + return array( + array(123), + array(new NonInvokable()) + ); + } + + /** + * Provider for service definitions + */ + public function serviceDefinitionProvider() + { + return array( + array(function ($value) { + $service = new Service(); + $service->value = $value; + + return $service; + }), + array(new Invokable()) + ); + } +} diff --git a/vendor/pimple/pimple/tests/Pimple/Tests/Service.php b/vendor/pimple/pimple/tests/Pimple/Tests/Service.php new file mode 100644 index 00000000..a5040627 --- /dev/null +++ b/vendor/pimple/pimple/tests/Pimple/Tests/Service.php @@ -0,0 +1,37 @@ + + */ +class Service +{ +} diff --git a/vendor/pimple/pimple/tests/bootstrap.php b/vendor/pimple/pimple/tests/bootstrap.php new file mode 100644 index 00000000..cdb7a436 --- /dev/null +++ b/vendor/pimple/pimple/tests/bootstrap.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require_once __DIR__.'/../lib/Pimple.php'; +require_once __DIR__.'/Pimple/Tests/Service.php'; +require_once __DIR__.'/Pimple/Tests/Invokable.php'; +require_once __DIR__.'/Pimple/Tests/NonInvokable.php'; 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..969235c4 --- /dev/null +++ b/vendor/silex/silex/.travis.yml @@ -0,0 +1,37 @@ +language: php + +sudo: false + +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\.28|~2\.0/~1.28/g' composer.json; php -d memory_limit=-1 `which composer` update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '3.0' ]; then sed -i 's/~2\.3|3\.0\.\*/3.0.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '2.8' ]; then sed -i 's/~2\.3|3\.0\.\*/2.8.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '' ]; then sed -i 's/~2\.3|3\.0\.\*/2.7.*@dev/g' composer.json; composer update; fi" + - sh -c "if [ '$SYMFONY_DEPS_VERSION' = '2.3' ]; then sed -i 's/~2\.3|3\.0\.\*/2.3.*@dev/g' composer.json; composer update; fi" + - composer install + +script: phpunit + +matrix: + include: + - php: 5.3 + - php: 5.4 + - php: 5.5 + - php: 5.6 + env: TWIG_VERSION=2.0 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=2.3 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=2.8 + - php: 5.6 + env: SYMFONY_DEPS_VERSION=3.0 + - php: 7.0 + - php: hhvm 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..d95d4535 --- /dev/null +++ b/vendor/silex/silex/README.rst @@ -0,0 +1,64 @@ +Silex, a simple Web Framework +============================= + +**WARNING** Silex 1.x is not maintained anymore. Please, upgrade to Silex 2.x. + +Silex is a PHP micro-framework to develop websites based on `Symfony +components`_:: + + get('/hello/{name}', function ($name) use ($app) { + return 'Hello '.$app->escape($name); + }); + + $app->run(); + +Silex works with PHP 5.3.9 or later. + +Installation +------------ + +The recommended way to install Silex is through `Composer`_: + +.. code-block:: bash + + composer require silex/silex "~1.3" + +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/bin/build b/vendor/silex/silex/bin/build new file mode 100644 index 00000000..e7270037 --- /dev/null +++ b/vendor/silex/silex/bin/build @@ -0,0 +1,67 @@ +#!/bin/sh + +PHP=`which php` +GIT=`which git` +DIR=`$PHP -r "echo dirname(dirname(realpath('$0')));"` + +if [ ! -d "$DIR/build" ]; then + mkdir -p $DIR/build +fi + +cd $DIR/build + +if [ ! -f "composer.phar" ]; then + curl -s http://getcomposer.org/installer 2>/dev/null | $PHP >/dev/null 2>/dev/null +else + $PHP composer.phar self-update >/dev/null 2>/dev/null +fi + +for TYPE in slim fat +do + if [ -d "$DIR/build/skeleton" ]; then + rm -rf $DIR/build/skeleton + fi + mkdir -p $DIR/build/skeleton + + cd "$DIR/build/skeleton" + + mkdir -p web/ + COMPOSER=$TYPE"_composer.json" + cp $DIR/bin/skeleton/$COMPOSER composer.json + cp $DIR/bin/skeleton/index.php web/index.php + + $PHP ../composer.phar install -q + + if [ -d "$DIR/build/tmp/silex" ]; then + rm -rf $DIR/build/tmp/silex + fi + mkdir -p $DIR/build/tmp/silex + + cd "$DIR/build/tmp/silex" + cp -r ../../skeleton/* . + + find . -name .DS_Store | xargs rm -rf - + find . -name .git | xargs rm -rf - + find . -name phpunit.xml.* | xargs rm -rf - + find . -type d -name Tests | xargs rm -rf - + find . -type d -name test* | xargs rm -rf - + find . -type d -name doc | xargs rm -rf - + find . -type d -name ext | xargs rm -rf - + + export COPY_EXTENDED_ATTRIBUTES_DISABLE=true + export COPYFILE_DISABLE=true + + cd "$DIR/build/tmp" + + if [ "slim" = "$TYPE" ]; then + NAME="silex" + else + NAME="silex_fat" + fi + + rm -f "$DIR/build/$NAME.*" + tar zcpf "$DIR/build/$NAME.tgz" silex + zip -rq "$DIR/build/$NAME.zip" silex + rm -rf "$DIR/build/tmp" + rm -rf "$DIR/build/skeleton" +done diff --git a/vendor/silex/silex/bin/compile b/vendor/silex/silex/bin/compile new file mode 100644 index 00000000..f0986edc --- /dev/null +++ b/vendor/silex/silex/bin/compile @@ -0,0 +1,9 @@ +#!/usr/bin/env php +compile(); diff --git a/vendor/silex/silex/bin/skeleton/fat_composer.json b/vendor/silex/silex/bin/skeleton/fat_composer.json new file mode 100644 index 00000000..4495d4f2 --- /dev/null +++ b/vendor/silex/silex/bin/skeleton/fat_composer.json @@ -0,0 +1,23 @@ +{ + "require": { + "silex/silex": "~1.1", + "symfony/browser-kit": "~2.3", + "symfony/console": "~2.3", + "symfony/config": "~2.3", + "symfony/css-selector": "~2.3", + "symfony/dom-crawler": "~2.3", + "symfony/filesystem": "~2.3", + "symfony/finder": "~2.3", + "symfony/form": "~2.3", + "symfony/locale": "~2.3", + "symfony/process": "~2.3", + "symfony/security": "~2.3", + "symfony/serializer": "~2.3", + "symfony/translation": "~2.3", + "symfony/validator": "~2.3", + "symfony/monolog-bridge": "~2.3", + "symfony/twig-bridge": "~2.3", + "doctrine/dbal": ">=2.2.0,<2.4.0-dev", + "swiftmailer/swiftmailer": "5.*" + } +} diff --git a/vendor/silex/silex/bin/skeleton/index.php b/vendor/silex/silex/bin/skeleton/index.php new file mode 100644 index 00000000..683c610e --- /dev/null +++ b/vendor/silex/silex/bin/skeleton/index.php @@ -0,0 +1,11 @@ +get('/hello', function () { + return 'Hello!'; +}); + +$app->run(); diff --git a/vendor/silex/silex/bin/skeleton/slim_composer.json b/vendor/silex/silex/bin/skeleton/slim_composer.json new file mode 100644 index 00000000..df5ed000 --- /dev/null +++ b/vendor/silex/silex/bin/skeleton/slim_composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "silex/silex": "~1.1" + } +} diff --git a/vendor/silex/silex/composer.json b/vendor/silex/silex/composer.json new file mode 100644 index 00000000..c2aada77 --- /dev/null +++ b/vendor/silex/silex/composer.json @@ -0,0 +1,60 @@ +{ + "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.3.9", + "pimple/pimple": "~1.0", + "symfony/event-dispatcher": "~2.3|3.0.*", + "symfony/http-foundation": "~2.3|3.0.*", + "symfony/http-kernel": "~2.3|3.0.*", + "symfony/routing": "~2.3|3.0.*" + }, + "require-dev": { + "symfony/security": "~2.3|3.0.*", + "symfony/config": "~2.3|3.0.*", + "symfony/intl": "~2.3|3.0.*", + "symfony/form": "~2.3|3.0.*", + "symfony/browser-kit": "~2.3|3.0.*", + "symfony/css-selector": "~2.3|3.0.*", + "symfony/debug": "~2.3|3.0.*", + "symfony/dom-crawler": "~2.3|3.0.*", + "symfony/finder": "~2.3|3.0.*", + "symfony/monolog-bridge": "~2.3|3.0.*", + "symfony/options-resolver": "~2.3|3.0.*", + "symfony/phpunit-bridge": "~2.7", + "symfony/process": "~2.3|3.0.*", + "symfony/serializer": "~2.3|3.0.*", + "symfony/translation": "~2.3|3.0.*", + "symfony/twig-bridge": "~2.3|3.0.*", + "symfony/validator": "~2.3|3.0.*", + "twig/twig": "~1.28|~2.0", + "doctrine/dbal": "~2.2", + "swiftmailer/swiftmailer": "~5", + "monolog/monolog": "^1.4.1" + }, + "autoload": { + "psr-4": { "Silex\\": "src/Silex" } + }, + "autoload-dev" : { + "psr-4": { "Silex\\Tests\\" : "tests/Silex/Tests" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.3.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..bf3c0654 --- /dev/null +++ b/vendor/silex/silex/doc/changelog.rst @@ -0,0 +1,311 @@ +Changelog +========= + +1.3.6 (2016-04-30) +------------------ + +**WARNING** This is the last release of the Silex 1.x branch. Please, upgrade +to Silex 2.x. + +* minor docs/tests changes + +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/assets.rst b/vendor/silex/silex/doc/cookbook/assets.rst new file mode 100644 index 00000000..41c75793 --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/assets.rst @@ -0,0 +1,66 @@ +Managing Assets in Templates +============================ + +A Silex application is not always hosted at the web root directory. To avoid +repeating the base path whenever you link to another page, it is highly +recommended to use the :doc:`URL generator service provider +`. + +But what about images, stylesheets, or JavaScript files? Their URLs are not +managed by the Silex router, but nonetheless, they need to get prefixed by the +base path. Fortunately, the Request object contain the application base path +that needs to be prepended:: + + // generate a link to the stylesheets in /css/styles.css + $request->getBasePath().'/css/styles.css'; + +And doing the same in a Twig template is as easy as it can get: + +.. code-block:: jinja + + {{ app.request.basepath }}/css/styles.css + +If your assets are hosted under a different host, you might want to abstract +the path by defining a Silex parameter:: + + $app['asset_path'] = 'http://assets.examples.com'; + +Using it in a template is as easy as before: + +.. code-block:: jinja + + {{ app.asset_path }}/css/styles.css + +If you need to implement some logic independently of the asset, define a +service instead:: + + $app['asset_path'] = $app->share(function () { + // implement whatever logic you need to determine the asset path + + return 'http://assets.examples.com'; + }); + +Usage is exactly the same as before: + +.. code-block:: jinja + + {{ app.asset_path }}/css/styles.css + +If the asset location depends on the asset type or path, you will need more +abstraction; here is one way to do that with a Twig function:: + + $app['twig'] = $app->share($app->extend('twig', function($twig, $app) { + $twig->addFunction(new \Twig_SimpleFunction('asset', function ($asset) { + // implement whatever logic you need to determine the asset path + + return sprintf('http://assets.examples.com/%s', ltrim($asset, '/')); + })); + + return $twig; + })); + +The ``asset`` function can then be used in your templates: + +.. code-block:: jinja + + {{ asset('/css/styles.css') }} 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..d76232bf --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/form_no_csrf.rst @@ -0,0 +1,35 @@ +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. By default, 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/index.rst b/vendor/silex/silex/doc/cookbook/index.rst new file mode 100644 index 00000000..f01b76bb --- /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 + assets + +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:`Managing Assets in Templates `. 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..720c8b69 --- /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] = $app->share(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'] = $app->share(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'] = $app->share(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..ddf0f7eb --- /dev/null +++ b/vendor/silex/silex/doc/cookbook/session_storage.rst @@ -0,0 +1,91 @@ +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'] = $app->share(function () use ($app) { + return new PDO( + $app['pdo.dsn'], + $app['pdo.user'], + $app['pdo.password'] + ); + }); + + $app['session.storage.handler'] = $app->share(function () use ($app) { + return new PdoSessionHandler( + $app['pdo'], + $app['session.db_options'], + $app['session.storage.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.db_options'] = array( + 'db_table' => 'session', + 'db_id_col' => 'session_id', + 'db_data_col' => 'session_value', + 'db_time_col' => 'session_time', + ); + + $app['session.storage.handler'] = $app->share(function () use ($app) { + return new PdoSessionHandler( + $app['db']->getWrappedConnection(), + $app['session.db_options'], + $app['session.storage.options'] + ); + }); + +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_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..2d478ffd --- /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\ClassMetadataFactory( + 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..99551b89 --- /dev/null +++ b/vendor/silex/silex/doc/intro.rst @@ -0,0 +1,53 @@ +Introduction +============ + +**WARNINIG** You are reading documentation for the Silex 1.x branch, which is +not maintained anymore. You should upgrade to Silex 2.x. + +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..5b3878bb --- /dev/null +++ b/vendor/silex/silex/doc/organizing_controllers.rst @@ -0,0 +1,73 @@ +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); + +.. 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, and ``/forum/`` to the forum 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..5246342f --- /dev/null +++ b/vendor/silex/silex/doc/providers.rst @@ -0,0 +1,219 @@ +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:`DoctrineServiceProvider ` +* :doc:`FormServiceProvider ` +* :doc:`HttpCacheServiceProvider ` +* :doc:`MonologServiceProvider ` +* :doc:`RememberMeServiceProvider ` +* :doc:`SecurityServiceProvider ` +* :doc:`SerializerServiceProvider ` +* :doc:`ServiceControllerServiceProvider ` +* :doc:`SessionServiceProvider ` +* :doc:`SwiftmailerServiceProvider ` +* :doc:`TranslationServiceProvider ` +* :doc:`TwigServiceProvider ` +* :doc:`UrlGeneratorServiceProvider ` +* :doc:`ValidatorServiceProvider ` + +.. 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 ``Silex\ServiceProviderInterface``:: + + interface ServiceProviderInterface + { + public function register(Application $app); + + public function boot(Application $app); + } + +The ``register()`` method defines services on the application which then may +make use of other services and parameters. + +The ``boot()`` method configures the application, just before it handles a +request. + +Here is an example of such a provider:: + + namespace Acme; + + use Silex\Application; + use Silex\ServiceProviderInterface; + + class HelloServiceProvider implements ServiceProviderInterface + { + public function register(Application $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) + { + } + } + +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:: + + $app = new Silex\Application(); + + $app->register(new Acme\HelloServiceProvider(), array( + 'hello.default_name' => 'Igor', + )); + + $app->get('/hello', function () use ($app) { + $name = $app['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\ControllerProviderInterface``:: + + interface ControllerProviderInterface + { + public function connect(Application $app); + } + +Here is an example of such a provider:: + + namespace Acme; + + use Silex\Application; + use Silex\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/doctrine.rst b/vendor/silex/silex/doc/providers/doctrine.rst new file mode 100644 index 00000000..50e7ea99 --- /dev/null +++ b/vendor/silex/silex/doc/providers/doctrine.rst @@ -0,0 +1,138 @@ +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:: + + Doctrine DBAL comes with the "fat" Silex archive but not with the regular + one. If you are using Composer, add it 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..aebd6893 --- /dev/null +++ b/vendor/silex/silex/doc/providers/form.rst @@ -0,0 +1,194 @@ +Form +==== + +The *FormServiceProvider* provides a service for building forms in +your application with the Symfony Form component. + +Parameters +---------- + +* **form.secret**: This secret value is used for generating and validating the + CSRF token for a specific page. It is very important for you to set this + value to a static randomly generated value, to prevent hijacking of your + forms. Defaults to ``md5(__DIR__)``. + +Services +-------- + +* **form.factory**: An instance of `FormFactory + `_, + that is used to build a form. + +* **form.csrf_provider**: An instance of an implementation of + `CsrfProviderInterface + `_ for Symfony 2.3 or + `CsrfTokenManagerInterface `_ for Symfony 2.4+. + +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. + + If you want to use validation with forms, do not forget to register the + :doc:`Validator provider `. + +.. note:: + + The Symfony Form Component and all its dependencies (optional or not) comes + with the "fat" Silex archive but not with the regular one. If you are using + Composer, add it 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/config`` and ``symfony/translation`` + components: + + .. code-block:: bash + + composer require symfony/validator symfony/config symfony/translation + + The Symfony Security CSRF component is used to protect forms against CSRF + attacks (as of Symfony 2.4+): + + .. code-block:: bash + + composer require symfony/security-csrf + + 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 symfony/config symfony/translation + +Usage +----- + +The FormServiceProvider provides a ``form.factory`` service. Here is a usage +example:: + + $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('form', $data) + ->add('name') + ->add('email') + ->add('billing_plan', 'choice', array( + 'choices' => array(1 => 'free', 2 => 'small_business', 3 => 'corporate'), + 'expanded' => true, + )) + ->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\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('form') + ->add('name', 'text', array( + 'constraints' => array(new Assert\NotBlank(), new Assert\Length(array('min' => 5))) + )) + ->add('email', 'text', array( + 'constraints' => new Assert\Email() + )) + ->add('billing_plan', 'choice', array( + 'choices' => array(1 => 'free', 2 => 'small_business', 3 => 'corporate'), + 'expanded' => true, + 'constraints' => new Assert\Choice(array(1, 2, 3)), + )) + ->getForm(); + +You can register form types by extending ``form.types``:: + + $app['form.types'] = $app->share($app->extend('form.types', function ($types) use ($app) { + $types[] = new YourFormType(); + + return $types; + })); + +You can register form extensions by extending ``form.extensions``:: + + $app['form.extensions'] = $app->share($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['form.type.extensions'] = $app->share($app->extend('form.type.extensions', function ($extensions) use ($app) { + $extensions[] = new YourFormTypeExtension(); + + return $extensions; + })); + +You can register form type guessers by extending ``form.type.guessers``:: + + $app['form.type.guessers'] = $app->share($app->extend('form.type.guessers', function ($guessers) use ($app) { + $guessers[] = new YourFormTypeGuesser(); + + return $guessers; + })); + +Traits +------ + +``Silex\Application\FormTrait`` adds the following shortcuts: + +* **form**: Creates a FormBuilder instance. + +.. code-block:: php + + $app->form($data); + +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..2d2c3526 --- /dev/null +++ b/vendor/silex/silex/doc/providers/http_fragment.rst @@ -0,0 +1,74 @@ +HTTP Fragment +============= + +The *HttpFragmentServiceProvider* provides support for the Symfony fragment +sub-framework, which allows you to embed fragments of HTML in a template. + +.. warning:: + + This service provider only work with Symfony 2.4+. + +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..9b31a2ca --- /dev/null +++ b/vendor/silex/silex/doc/providers/index.rst @@ -0,0 +1,21 @@ +Built-in Service Providers +========================== + +.. toctree:: + :maxdepth: 1 + + twig + url_generator + monolog + session + swiftmailer + translation + validator + form + http_cache + http_fragment + security + remember_me + serializer + service_controller + doctrine diff --git a/vendor/silex/silex/doc/providers/monolog.rst b/vendor/silex/silex/doc/providers/monolog.rst new file mode 100644 index 00000000..e922c965 --- /dev/null +++ b/vendor/silex/silex/doc/providers/monolog.rst @@ -0,0 +1,105 @@ +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``. + +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:: + + Monolog comes with the "fat" Silex archive but not with the regular one. + If you are using Composer, add it 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['monolog'] = $app->share($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 informations logged. + +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..a1fa9760 --- /dev/null +++ b/vendor/silex/silex/doc/providers/security.rst @@ -0,0 +1,695 @@ +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``. + +Services +-------- + +* **security**: The main entry point for the security provider. Use it to get + the current user token (only for Symfony up to 2.5). + +* **security.token_storage**: Gives access to the user token (Symfony 2.6+). + +* **security.authorization_checker**: Allows to check authorizations for the + users (Symfony 2.6+). + +* **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 (default to use a digest algorithm for all users). + +* **security.encoder.digest**: The encoder to use by default for all users. + +* **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:: + + The Symfony Security Component comes with the "fat" Silex archive but not + with the regular one. If you are using Composer, add it as a dependency: + + .. code-block:: bash + + composer require symfony/security + +.. 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(); + +.. caution:: + + If you're using a form to authenticate users, you need to enable + ``SessionServiceProvider``. + +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:: + + // Symfony 2.6+ + $token = $app['security.token_storage']->getToken(); + + // Symfony 2.3/2.5 + $token = $app['security']->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', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + ), + ), + ); + +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', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + ), + ), + ); + +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', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + ), + ), + ); + +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:: + + // Symfony 2.6+ + if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) { + // ... + } + + // Symfony 2.3/2.5 + if ($app['security']->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' => $app->share(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' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==', + 'roles' => 'ROLE_USER' + )); + + $app['db']->insert('users', array( + 'username' => 'admin', + 'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==', + '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 ``sha512`` algorithm to encode passwords. +Additionally, the password is encoded multiple times and converted to base64. +You can change these defaults by overriding the ``security.encoder.digest`` +service:: + + use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; + + $app['security.encoder.digest'] = $app->share(function ($app) { + // use the sha1 algorithm + // don't base64 encode the password + // use only 1 iteration + return new MessageDigestPasswordEncoder('sha1', false, 1); + }); + +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'] = $app->share(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'] = $app->share(function () use ($app) { + // use 'security' instead of 'security.token_storage' on Symfony <2.6 + 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`_. + +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..8ba55933 --- /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:: + + The *SerializerServiceProvider* relies on Symfony's `Serializer Component + `_, + which comes with the "fat" Silex archive but not with the regular + one. If you are using Composer, add it 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\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 ($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 = $app['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" => $app['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..03a41fde --- /dev/null +++ b/vendor/silex/silex/doc/providers/service_controller.rst @@ -0,0 +1,116 @@ +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'] = $app->share(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'] = $app->share(function() use ($app) { + return new PostController($app['posts.repository']); + }); + + $app->get('/posts.json', "posts.controller:indexJsonAction"); diff --git a/vendor/silex/silex/doc/providers/session.rst b/vendor/silex/silex/doc/providers/session.rst new file mode 100644 index 00000000..b8debe25 --- /dev/null +++ b/vendor/silex/silex/doc/providers/session.rst @@ -0,0 +1,102 @@ +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()); + +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\Response; + + $app->get('/login', function () use ($app) { + $username = $app['request']->server->get('PHP_AUTH_USER', false); + $password = $app['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..11ace311 --- /dev/null +++ b/vendor/silex/silex/doc/providers/swiftmailer.rst @@ -0,0 +1,135 @@ +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 + ); + +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:: + + SwiftMailer comes with the "fat" Silex archive but not with the regular + one. If you are using Composer, add it as a dependency: + + .. code-block:: bash + + composer require swiftmailer/swiftmailer + +Usage +----- + +The Swiftmailer provider provides a ``mailer`` service:: + + $app->post('/feedback', function () use ($app) { + $request = $app['request']; + + $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..63d12185 --- /dev/null +++ b/vendor/silex/silex/doc/providers/translation.rst @@ -0,0 +1,201 @@ +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\TranslationServiceProvider(), array( + 'locale_fallbacks' => array('en'), + )); + +.. note:: + + The Symfony Translation Component comes with the "fat" Silex archive but + not with the regular one. If you are using Composer, add it 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['translator'] = $app->share($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: + +.. code-block:: jinja + + {{ app.translator.trans('translation_key') }} + +Moreover, when using the Twig bridge provided by Symfony (see +:doc:`TwigServiceProvider `), you will be allowed to translate +strings in the Twig way: + +.. 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..fe12449f --- /dev/null +++ b/vendor/silex/silex/doc/providers/twig.rst @@ -0,0 +1,177 @@ +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). + +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:: + + Twig comes with the "fat" Silex archive but not with the regular one. If + you are using Composer, add it as a dependency: + + .. code-block:: bash + + composer require twig/twig + +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: + +* **UrlGeneratorServiceProvider**: If you are using the + ``UrlGeneratorServiceProvider``, you will have access to the ``path()`` and + ``url()`` functions. You can find more information in the `Symfony Routing + documentation + `_. + +* **TranslationServiceProvider**: 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 + `_. + +* **FormServiceProvider**: 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 + `_. + +* **SecurityServiceProvider**: 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 + `_. + +Usage +----- + +The Twig provider provides a ``twig`` service:: + + $app->get('/hello/{name}', function ($name) use ($app) { + return $app['twig']->render('hello.twig', array( + 'name' => $name, + )); + }); + +This will render a file named ``views/hello.twig``. + +In any Twig template, the ``app`` variable refers to the Application object. +So you can access any service from within your view. For example to access +``$app['request']->getHost()``, just put this in your template: + +.. code-block:: jinja + + {{ app.request.host }} + +A ``render`` function is also registered to help you render another controller +from a template: + +.. code-block:: jinja + + {{ render(app.request.baseUrl ~ '/sidebar') }} + + {# or if you are also using the UrlGeneratorServiceProvider #} + {{ 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['twig'] = $app->share($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/url_generator.rst b/vendor/silex/silex/doc/providers/url_generator.rst new file mode 100644 index 00000000..a8ed415e --- /dev/null +++ b/vendor/silex/silex/doc/providers/url_generator.rst @@ -0,0 +1,79 @@ +URL Generator +============= + +The *UrlGeneratorServiceProvider* provides a service for generating URLs for +named routes. + +Parameters +---------- + +None. + +Services +-------- + +* **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. + +Registering +----------- + +.. code-block:: php + + $app->register(new Silex\Provider\UrlGeneratorServiceProvider()); + +Usage +----- + +The UrlGenerator provider provides a ``url_generator`` service:: + + $app->get('/', function () { + return 'welcome to the homepage'; + }) + ->bind('homepage'); + + $app->get('/hello/{name}', function ($name) { + return "Hello $name!"; + }) + ->bind('hello'); + + $app->get('/navigation', function () use ($app) { + return 'Home'. + ' | '. + 'Hello Igor'; + }); + +When using Twig, the service can be used like this: + +.. code-block:: jinja + + {{ app.url_generator.generate('homepage') }} + +Moreover, if you have ``twig-bridge`` as a Composer dep, you will have access +to the ``path()`` and ``url()`` functions: + +.. 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 #} + +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'); diff --git a/vendor/silex/silex/doc/providers/validator.rst b/vendor/silex/silex/doc/providers/validator.rst new file mode 100644 index 00000000..6691fec9 --- /dev/null +++ b/vendor/silex/silex/doc/providers/validator.rst @@ -0,0 +1,218 @@ +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:: + + The Symfony Validator Component comes with the "fat" Silex archive but not + with the regular one. If you are using Composer, add it 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; + + class Book + { + public $title; + public $author; + } + + class Author + { + public $first_name; + public $last_name; + } + + $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; + + $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/services.rst b/vendor/silex/silex/doc/services.rst new file mode 100644 index 00000000..e29bd96e --- /dev/null +++ b/vendor/silex/silex/doc/services.rst @@ -0,0 +1,256 @@ +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`` all of this applies to Silex as +well:: + + $container = new Pimple(); + +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']; + +Every time you call ``$app['some_service']``, a new instance of the service is +created. + +Shared services +~~~~~~~~~~~~~~~ + +You may want to use the same instance of a service across all of your code. In +order to do that you can make a *shared* service:: + + $app['some_service'] = $app->share(function () { + return new Service(); + }); + +This will create the service on first invocation, and then return the existing +instance on any subsequent access. + +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. + +.. note:: + + This also works for shared services. + +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'] = $app->share(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**: Contains the current request object, which is an instance of + `Request + `_. + It gives you access to ``GET``, ``POST`` parameters and lots more! + + Example usage:: + + $id = $app['request']->get('id'); + + This 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. + +* **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 :doc:`UrlGenerator `. + +* **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. + +.. note:: + + All of these Silex core services are shared. + +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 by the ``UrlGeneratorProvider``. + +* **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 by the ``UrlGeneratorProvider``. + +* **locale** (optional): 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. + +* **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..2f56325b --- /dev/null +++ b/vendor/silex/silex/doc/usage.rst @@ -0,0 +1,782 @@ +Usage +===== + +Installation +------------ + +If you want to get started fast, `download`_ Silex as an archive and extract +it, you should have the following directory structure: + +.. code-block:: text + + ├── composer.json + ├── composer.lock + ├── vendor + │ └── ... + └── web + └── index.php + +If you want more flexibility, use Composer_ instead: + +.. code-block:: bash + + composer require silex/silex:~1.3 + +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'] = $app->share(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+'); + +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 (such as ``UrlGeneratorProvider``) 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 () { /* ... */ }) + ; + +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; + + $app->error(function (\Exception $e, $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; + + $app->error(function (\Exception $e, $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:: + + $app->error(function (\LogicException $e, $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; + + $app->error(function (\Exception $e, $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:: + + If you are using ``UrlGeneratorProvider``, you can also generate the URI:: + + $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. + +.. caution:: + + You need to use PHP 5.4 or later to benefit from this feature. + +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:: + + $app->get('/name', function (Silex\Application $app) { + $name = $app['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:: + + $app->get('/name.json', function (Silex\Application $app) { + $name = $app['request']->get('name'); + + return $app->json(array('name' => $name)); + }); + +.. _download: http://silex.sensiolabs.org/download +.. _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 diff --git a/vendor/silex/silex/doc/web_servers.rst b/vendor/silex/silex/doc/web_servers.rst new file mode 100644 index 00000000..af689b10 --- /dev/null +++ b/vendor/silex/silex/doc/web_servers.rst @@ -0,0 +1,166 @@ +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. + +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 5.4 +------- + +PHP 5.4 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/Application.php b/vendor/silex/silex/src/Silex/Application.php new file mode 100644 index 00000000..76b593e7 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application.php @@ -0,0 +1,600 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Kernel; +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\EventListener\ResponseListener; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Silex\EventListener\LocaleListener; +use Silex\EventListener\MiddlewareListener; +use Silex\EventListener\ConverterListener; +use Silex\EventListener\StringToResponseListener; + +/** + * The Silex framework class. + * + * @author Fabien Potencier + */ +class Application extends \Pimple implements HttpKernelInterface, TerminableInterface +{ + const VERSION = '1.3.6'; + + 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(); + + $app = $this; + + $this['logger'] = null; + + $this['routes'] = $this->share(function () { + return new RouteCollection(); + }); + + $this['controllers'] = $this->share(function () use ($app) { + return $app['controllers_factory']; + }); + + $this['controllers_factory'] = function () use ($app) { + return new ControllerCollection($app['route_factory']); + }; + + $this['route_class'] = 'Silex\\Route'; + $this['route_factory'] = function () use ($app) { + return new $app['route_class'](); + }; + + $this['exception_handler'] = $this->share(function () use ($app) { + return new ExceptionHandler($app['debug']); + }); + + $this['dispatcher_class'] = 'Symfony\\Component\\EventDispatcher\\EventDispatcher'; + $this['dispatcher'] = $this->share(function () use ($app) { + /* + * @var EventDispatcherInterface + */ + $dispatcher = new $app['dispatcher_class'](); + + $urlMatcher = new LazyUrlMatcher(function () use ($app) { + return $app['url_matcher']; + }); + if (Kernel::VERSION_ID >= 20800) { + $dispatcher->addSubscriber(new RouterListener($urlMatcher, $app['request_stack'], $app['request_context'], $app['logger'])); + } else { + $dispatcher->addSubscriber(new RouterListener($urlMatcher, $app['request_context'], $app['logger'], $app['request_stack'])); + } + $dispatcher->addSubscriber(new LocaleListener($app, $urlMatcher, $app['request_stack'])); + if (isset($app['exception_handler'])) { + $dispatcher->addSubscriber($app['exception_handler']); + } + $dispatcher->addSubscriber(new ResponseListener($app['charset'])); + $dispatcher->addSubscriber(new MiddlewareListener($app)); + $dispatcher->addSubscriber(new ConverterListener($app['routes'], $app['callback_resolver'])); + $dispatcher->addSubscriber(new StringToResponseListener()); + + return $dispatcher; + }); + + $this['callback_resolver'] = $this->share(function () use ($app) { + return new CallbackResolver($app); + }); + + $this['resolver'] = $this->share(function () use ($app) { + return new ControllerResolver($app, $app['logger']); + }); + + $this['kernel'] = $this->share(function () use ($app) { + return new HttpKernel($app['dispatcher'], $app['resolver'], $app['request_stack']); + }); + + $this['request_stack'] = $this->share(function () use ($app) { + if (class_exists('Symfony\Component\HttpFoundation\RequestStack')) { + return new RequestStack(); + } + }); + + $this['request_context'] = $this->share(function () use ($app) { + $context = new RequestContext(); + + $context->setHttpPort($app['request.http_port']); + $context->setHttpsPort($app['request.https_port']); + + return $context; + }); + + $this['url_matcher'] = $this->share(function () use ($app) { + return new RedirectableUrlMatcher($app['routes'], $app['request_context']); + }); + + $this['request_error'] = $this->protect(function () { + throw new \RuntimeException('Accessed request service outside of request scope. Try moving that call to a before handler or controller.'); + }); + + $this['request'] = $this['request_error']; + + $this['request.http_port'] = 80; + $this['request.https_port'] = 443; + $this['debug'] = false; + $this['charset'] = 'UTF-8'; + $this['locale'] = 'en'; + + 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; + + $provider->register($this); + + foreach ($values as $key => $value) { + $this[$key] = $value; + } + + 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) { + foreach ($this->providers as $provider) { + $provider->boot($this); + } + + $this->booted = true; + } + } + + /** + * 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['dispatcher'] = $this->share($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 (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + 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 (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + 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. + * + * @param string $prefix The route prefix + */ + public function flush($prefix = '') + { + $this['routes']->addCollection($this['controllers']->flush($prefix)); + } + + /** + * 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 + * + * @throws \RuntimeException When the feature is not supported, before http-foundation v2.2 + */ + 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|ControllerProviderInterface $controllers A ControllerCollection or a ControllerProviderInterface instance + * + * @return Application + */ + 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) { + throw new \LogicException('The "mount" method takes either a "ControllerCollection" or a "ControllerProviderInterface" instance.'); + } + + $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(); + } + + $current = HttpKernelInterface::SUB_REQUEST === $type ? $this['request'] : $this['request_error']; + + $this['request'] = $request; + + $this->flush(); + + $response = $this['kernel']->handle($request, $type, $catch); + + $this['request'] = $current; + + return $response; + } + + /** + * {@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..46e5e88a --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/FormTrait.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\Application; + +use Symfony\Component\Form\FormBuilder; + +/** + * Form trait. + * + * @author Fabien Potencier + */ +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 + * + * @return FormBuilder + */ + public function form($data = null, array $options = array()) + { + $name = 'Symfony\Component\Form\Extension\Core\Type\FormType'; + // BC with Symfony < 2.8 + if (!class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType')) { + $name = 'form'; + } + + return $this['form.factory']->createBuilder($name, $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..6bca5e09 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Application/SecurityTrait.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 Silex\Application; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Security trait. + * + * @author Fabien Potencier + */ +trait SecurityTrait +{ + /** + * Gets a user from the Security context. + * + * @return mixed + * + * @see TokenInterface::getUser() + * @deprecated since 1.3, to be removed in 3.0 + */ + public function user() + { + return $this['user']; + } + + /** + * 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..859d86e2 --- /dev/null +++ b/vendor/silex/silex/src/Silex/CallbackResolver.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 Silex; + +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(\Pimple $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); + } + + /** + * Returns a callable given its string representation. + * + * @param string $name + * + * @return array A callable array + * + * @throws \InvalidArgumentException In case the method does not exist. + */ + public function convertCallback($name) + { + list($service, $method) = explode(':', $name, 2); + + if (!isset($this->app[$service])) { + throw new \InvalidArgumentException(sprintf('Service "%s" does not exist.', $service)); + } + + return array($this->app[$service], $method); + } + + /** + * Returns a callable given its string representation if it is a valid service method. + * + * @param string $name + * + * @return array A callable array + * + * @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/ConstraintValidatorFactory.php b/vendor/silex/silex/src/Silex/ConstraintValidatorFactory.php new file mode 100644 index 00000000..0bdc82ce --- /dev/null +++ b/vendor/silex/silex/src/Silex/ConstraintValidatorFactory.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; + +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 \Pimple + */ + protected $container; + + /** + * @var array + */ + protected $serviceNames; + + /** + * Constructor. + * + * @param \Pimple $container DI container + * @param array $serviceNames Validator service names + */ + public function __construct(\Pimple $container, array $serviceNames = array(), $propertyAccessor = null) + { + // for BC with 2.3 + if (method_exists('Symfony\Component\Validator\Constraint\BaseConstraintValidatorFactory', '__construct')) { + 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/Controller.php b/vendor/silex/silex/src/Silex/Controller.php new file mode 100644 index 00000000..96bc6b0b --- /dev/null +++ b/vendor/silex/silex/src/Silex/Controller.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 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) + * + * @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..b0374e97 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ControllerCollection.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace 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) + * + * @author Igor Wiedler + * @author Fabien Potencier + */ +class ControllerCollection +{ + protected $controllers = array(); + protected $defaultRoute; + protected $defaultController; + protected $prefix; + + public function __construct(Route $defaultRoute) + { + $this->defaultRoute = $defaultRoute; + $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 $controllers A ControllerCollection instance + */ + public function mount($prefix, ControllerCollection $controllers) + { + $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) { + if ($controller instanceof Controller) { + call_user_func_array(array($controller, $method), $arguments); + } + } + + return $this; + } + + /** + * Persists and freezes staged controllers. + * + * @param string $prefix + * + * @return RouteCollection A RouteCollection instance + */ + public function flush($prefix = '') + { + return $this->doFlush($prefix, new RouteCollection()); + } + + 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 = $controller->generateRouteName(''); + while ($routes->get($name)) { + $name .= '_'; + } + $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/ControllerProviderInterface.php b/vendor/silex/silex/src/Silex/ControllerProviderInterface.php new file mode 100644 index 00000000..2e1f7e6a --- /dev/null +++ b/vendor/silex/silex/src/Silex/ControllerProviderInterface.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 Silex; + +/** + * 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/ControllerResolver.php b/vendor/silex/silex/src/Silex/ControllerResolver.php new file mode 100644 index 00000000..5955f8f8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ControllerResolver.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace 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 + */ +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/LocaleListener.php b/vendor/silex/silex/src/Silex/EventListener/LocaleListener.php new file mode 100644 index 00000000..0b20d7c8 --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/LocaleListener.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\EventListener; + +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\EventListener\LocaleListener as BaseLocaleListener; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Silex\Application; + +/** + * Initializes the locale based on the current request. + * + * @author Fabien Potencier + */ +class LocaleListener extends BaseLocaleListener +{ + protected $app; + + public function __construct(Application $app, RequestContextAwareInterface $router = null, RequestStack $requestStack = null) + { + if (Kernel::VERSION_ID >= 20800) { + parent::__construct($requestStack, $app['locale'], $router); + } else { + parent::__construct($app['locale'], $router, $requestStack); + } + + $this->app = $app; + } + + public function onKernelRequest(GetResponseEvent $event) + { + parent::onKernelRequest($event); + + $this->app['locale'] = $event->getRequest()->getLocale(); + } +} 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..9211d072 --- /dev/null +++ b/vendor/silex/silex/src/Silex/EventListener/LogListener.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; +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; + +/** + * Log request, response and exceptions. + */ +class LogListener implements EventSubscriberInterface +{ + protected $logger; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Logs master requests on event KernelEvents::REQUEST. + * + * @param GetResponseEvent $event + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + return; + } + + $this->logRequest($event->getRequest()); + } + + /** + * Logs master response on event KernelEvents::RESPONSE. + * + * @param FilterResponseEvent $event + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + 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->info('> '.$request->getMethod().' '.$request->getRequestUri()); + } + + /** + * Logs a response. + * + * @param Response $response + */ + protected function logResponse(Response $response) + { + if ($response instanceof RedirectResponse) { + $this->logger->info('< '.$response->getStatusCode().' '.$response->getTargetUrl()); + } else { + $this->logger->info('< '.$response->getStatusCode()); + } + } + + /** + * Logs an exception. + * + * @param \Exception $e + */ + protected function logException(\Exception $e) + { + $message = sprintf('%s: %s (uncaught exception) at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()); + + if ($e instanceof HttpExceptionInterface && $e->getStatusCode() < 500) { + $this->logger->error($message, array('exception' => $e)); + } else { + $this->logger->critical($message, 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..451566b9 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ExceptionHandler.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace 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; + +/** + * Defaults exception handler. + * + * @author Fabien Potencier + */ +class ExceptionHandler implements EventSubscriberInterface +{ + protected $debug; + protected $enabled; + + public function __construct($debug) + { + $this->debug = $debug; + $this->enabled = true; + } + + /** + * @deprecated since 1.3, to be removed in 2.0 + */ + public function disable() + { + $this->enabled = false; + } + + public function onSilexError(GetResponseForExceptionEvent $event) + { + if (!$this->enabled) { + return; + } + + $handler = new DebugExceptionHandler($this->debug); + + if (method_exists($handler, 'getHtml')) { + $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')); + } else { + // BC with Symfony < 2.8 + $response = $handler->createResponse($event->getException()); + } + + $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..fc0d2318 --- /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, $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/HttpCache.php b/vendor/silex/silex/src/Silex/HttpCache.php new file mode 100644 index 00000000..ebd65dd9 --- /dev/null +++ b/vendor/silex/silex/src/Silex/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; + +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/LazyUrlMatcher.php b/vendor/silex/silex/src/Silex/LazyUrlMatcher.php new file mode 100644 index 00000000..20055031 --- /dev/null +++ b/vendor/silex/silex/src/Silex/LazyUrlMatcher.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 Silex; + +use Symfony\Component\Routing\RequestContext as SymfonyRequestContext; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * Implements a lazy UrlMatcher. + * + * @author Igor Wiedler + */ +class LazyUrlMatcher implements UrlMatcherInterface +{ + private $factory; + + public function __construct(\Closure $factory) + { + $this->factory = $factory; + } + + /** + * Returns the corresponding UrlMatcherInterface instance. + * + * @return UrlMatcherInterface + */ + public function getUrlMatcher() + { + $urlMatcher = call_user_func($this->factory); + if (!$urlMatcher instanceof UrlMatcherInterface) { + throw new \LogicException('Factory supplied to LazyUrlMatcher must return implementation of UrlMatcherInterface.'); + } + + return $urlMatcher; + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + return $this->getUrlMatcher()->match($pathinfo); + } + + /** + * {@inheritdoc} + */ + public function setContext(SymfonyRequestContext $context) + { + $this->getUrlMatcher()->setContext($context); + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->getUrlMatcher()->getContext(); + } +} 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..02693367 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.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 Silex\Provider; + +use Silex\Application; +use Silex\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(Application $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'] = $app->share(function ($app) { + $app['dbs.options.initializer'](); + + $dbs = new \Pimple(); + 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] = $dbs->share(function ($dbs) use ($options, $config, $manager) { + return DriverManager::getConnection($options, $config, $manager); + }); + } + + return $dbs; + }); + + $app['dbs.config'] = $app->share(function ($app) { + $app['dbs.options.initializer'](); + + $configs = new \Pimple(); + foreach ($app['dbs.options'] as $name => $options) { + $configs[$name] = new Configuration(); + + if (isset($app['logger']) && class_exists('Symfony\Bridge\Doctrine\Logger\DbalLogger')) { + $configs[$name]->setSQLLogger(new DbalLogger($app['logger'], isset($app['stopwatch']) ? $app['stopwatch'] : null)); + } + } + + return $configs; + }); + + $app['dbs.event_manager'] = $app->share(function ($app) { + $app['dbs.options.initializer'](); + + $managers = new \Pimple(); + foreach ($app['dbs.options'] as $name => $options) { + $managers[$name] = new EventManager(); + } + + return $managers; + }); + + // shortcuts for the "first" DB + $app['db'] = $app->share(function ($app) { + $dbs = $app['dbs']; + + return $dbs[$app['dbs.default']]; + }); + + $app['db.config'] = $app->share(function ($app) { + $dbs = $app['dbs.config']; + + return $dbs[$app['dbs.default']]; + }); + + $app['db.event_manager'] = $app->share(function ($app) { + $dbs = $app['dbs.event_manager']; + + return $dbs[$app['dbs.default']]; + }); + } + + public function boot(Application $app) + { + } +} 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..faf94238 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/FormServiceProvider.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 Silex\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +use Symfony\Component\Form\Extension\Csrf\CsrfExtension; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider; +use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension; +use Symfony\Component\Form\Extension\Validator\ValidatorExtension as FormValidatorExtension; +use Symfony\Component\Form\Forms; +use Symfony\Component\Form\ResolvedFormTypeFactory; +use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; + +/** + * Symfony Form component Provider. + * + * @author Fabien Potencier + */ +class FormServiceProvider implements ServiceProviderInterface +{ + public function register(Application $app) + { + if (!class_exists('Locale') && !class_exists('Symfony\Component\Locale\Stub\StubLocale')) { + throw new \RuntimeException('You must either install the PHP intl extension or the Symfony Locale Component to use the Form extension.'); + } + + if (!class_exists('Locale')) { + $r = new \ReflectionClass('Symfony\Component\Locale\Stub\StubLocale'); + $path = dirname(dirname($r->getFilename())).'/Resources/stubs'; + + require_once $path.'/functions.php'; + require_once $path.'/Collator.php'; + require_once $path.'/IntlDateFormatter.php'; + require_once $path.'/Locale.php'; + require_once $path.'/NumberFormatter.php'; + } + + $app['form.secret'] = md5(__DIR__); + + $app['form.types'] = $app->share(function ($app) { + return array(); + }); + + $app['form.type.extensions'] = $app->share(function ($app) { + return array(); + }); + + $app['form.type.guessers'] = $app->share(function ($app) { + return array(); + }); + + $app['form.extension.csrf'] = $app->share(function ($app) { + if (isset($app['translator'])) { + return new CsrfExtension($app['form.csrf_provider'], $app['translator']); + } + + return new CsrfExtension($app['form.csrf_provider']); + }); + + $app['form.extensions'] = $app->share(function ($app) { + $extensions = array( + $app['form.extension.csrf'], + new HttpFoundationExtension(), + ); + + if (isset($app['validator'])) { + $extensions[] = new FormValidatorExtension($app['validator']); + } + + return $extensions; + }); + + $app['form.factory'] = $app->share(function ($app) { + return Forms::createFormFactoryBuilder() + ->addExtensions($app['form.extensions']) + ->addTypes($app['form.types']) + ->addTypeExtensions($app['form.type.extensions']) + ->addTypeGuessers($app['form.type.guessers']) + ->setResolvedTypeFactory($app['form.resolved_type_factory']) + ->getFormFactory() + ; + }); + + $app['form.resolved_type_factory'] = $app->share(function ($app) { + return new ResolvedFormTypeFactory(); + }); + + $app['form.csrf_provider'] = $app->share(function ($app) { + if (!class_exists('Symfony\Component\Form\Extension\DataCollector\DataCollectorExtension')) { + // Symfony 2.3 + if (isset($app['session'])) { + return new SessionCsrfProvider($app['session'], $app['form.secret']); + } + + return new DefaultCsrfProvider($app['form.secret']); + } else { + // Symfony 2.4+ + $storage = isset($app['session']) ? new SessionTokenStorage($app['session']) : new NativeSessionTokenStorage(); + + return new CsrfTokenManager(null, $storage); + } + }); + } + + public function boot(Application $app) + { + } +} 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..1281c814 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpCacheServiceProvider.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\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +use Silex\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\EventListener\EsiListener; +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; + +/** + * Symfony HttpKernel component Provider for HTTP cache. + * + * @author Fabien Potencier + */ +class HttpCacheServiceProvider implements ServiceProviderInterface +{ + public function register(Application $app) + { + $app['http_cache'] = $app->share(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'] = $app->share(function ($app) { + return new Esi(); + }); + + $app['http_cache.store'] = $app->share(function ($app) { + return new Store($app['http_cache.cache_dir']); + }); + + $app['http_cache.esi_listener'] = $app->share(function ($app) { + if (class_exists('Symfony\Component\HttpKernel\EventListener\SurrogateListener')) { + return new SurrogateListener($app['http_cache.esi']); + } + + return new EsiListener($app['http_cache.esi']); + }); + + $app['http_cache.options'] = array(); + } + + public function boot(Application $app) + { + $app['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..082fb097 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.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 Silex\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +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. + * + * This service provider requires Symfony 2.4+. + * + * @author Fabien Potencier + */ +class HttpFragmentServiceProvider implements ServiceProviderInterface +{ + public function register(Application $app) + { + if (!class_exists('Symfony\Component\HttpFoundation\RequestStack')) { + throw new \LogicException('The HTTP Fragment service provider only works with Symfony 2.4+.'); + } + + $app['fragment.handler'] = $app->share(function ($app) { + if (Kernel::VERSION_ID >= 20800) { + return new FragmentHandler($app['request_stack'], $app['fragment.renderers'], $app['debug']); + } + + return new FragmentHandler($app['fragment.renderers'], $app['debug'], $app['request_stack']); + }); + + $app['fragment.renderer.inline'] = $app->share(function ($app) { + $renderer = new InlineFragmentRenderer($app['kernel'], $app['dispatcher']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }); + + $app['fragment.renderer.hinclude'] = $app->share(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'] = $app->share(function ($app) { + $renderer = new EsiFragmentRenderer($app['http_cache.esi'], $app['fragment.renderer.inline']); + $renderer->setFragmentPath($app['fragment.path']); + + return $renderer; + }); + + $app['fragment.listener'] = $app->share(function ($app) { + return new FragmentListener($app['uri_signer'], $app['fragment.path']); + }); + + $app['uri_signer'] = $app->share(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'] = $app->share(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 boot(Application $app) + { + $app['dispatcher']->addSubscriber($app['fragment.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..acb21ef5 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/MonologServiceProvider.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 Monolog\Logger; +use Monolog\Handler\StreamHandler; +use Silex\Application; +use Silex\ServiceProviderInterface; +use Symfony\Bridge\Monolog\Handler\DebugHandler; +use Silex\EventListener\LogListener; + +/** + * Monolog Provider. + * + * @author Fabien Potencier + */ +class MonologServiceProvider implements ServiceProviderInterface +{ + public function register(Application $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); + }; + } + + $app['monolog.logger.class'] = $bridge ? 'Symfony\Bridge\Monolog\Logger' : 'Monolog\Logger'; + + $app['monolog'] = $app->share(function ($app) { + $log = new $app['monolog.logger.class']($app['monolog.name']); + + $log->pushHandler($app['monolog.handler']); + + if ($app['debug'] && isset($app['monolog.handler.debug'])) { + $log->pushHandler($app['monolog.handler.debug']); + } + + return $log; + }); + + $app['monolog.handler'] = function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + return new StreamHandler($app['monolog.logfile'], $level, $app['monolog.bubble'], $app['monolog.permission']); + }; + + $app['monolog.level'] = function () { + return Logger::DEBUG; + }; + + $app['monolog.listener'] = $app->share(function () use ($app) { + return new LogListener($app['logger']); + }); + + $app['monolog.name'] = 'myapp'; + $app['monolog.bubble'] = true; + $app['monolog.permission'] = null; + } + + public function boot(Application $app) + { + 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..61890d95 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/RememberMeServiceProvider.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 Silex\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +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 +{ + public function register(Application $app) + { + $app['security.remember_me.response_listener'] = $app->share(function () { + 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 $app->share(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 $app->share(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 $app->share(function () use ($app, $name, $options) { + return new RememberMeAuthenticationProvider($app['security.user_checker'], $options['key'], $name); + }); + }); + } + + public function boot(Application $app) + { + if (!isset($app['security'])) { + throw new \LogicException('You must register the SecurityServiceProvider to use the RememberMeServiceProvider'); + } + + $app['dispatcher']->addSubscriber($app['security.remember_me.response_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..cbf3ee1b --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SecurityServiceProvider.php @@ -0,0 +1,597 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\SecurityContext; +use Symfony\Component\Security\Core\SecurityContextInterface; +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\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; + +/** + * Symfony Security component Provider. + * + * @author Fabien Potencier + */ +class SecurityServiceProvider implements ServiceProviderInterface +{ + protected $fakeRoutes; + + public function register(Application $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; + + $r = new \ReflectionMethod('Symfony\Component\Security\Http\Firewall\ContextListener', '__construct'); + $params = $r->getParameters(); + if ('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' === $params[0]->getClass()->getName()) { + $app['security.authorization_checker'] = $app->share(function ($app) { + return new AuthorizationChecker($app['security.token_storage'], $app['security.authentication_manager'], $app['security.access_manager']); + }); + + $app['security.token_storage'] = $app->share(function ($app) { + return new TokenStorage(); + }); + + $app['security'] = $app->share(function ($app) { + // Deprecated, to be removed in 2.0 + return new SecurityContext($app['security.token_storage'], $app['security.authorization_checker']); + }); + } else { + $app['security.token_storage'] = $app['security.authorization_checker'] = $app->share(function ($app) { + return $app['security']; + }); + + $app['security'] = $app->share(function ($app) { + // Deprecated, to be removed in 2.0 + return new SecurityContext($app['security.authentication_manager'], $app['security.access_manager']); + }); + } + + $app['user'] = function ($app) { + if (null === $token = $app['security.token_storage']->getToken()) { + return; + } + + if (!is_object($user = $token->getUser())) { + return; + } + + return $user; + }; + + $app['security.authentication_manager'] = $app->share(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'] = $app->share(function ($app) { + return new EncoderFactory(array( + 'Symfony\Component\Security\Core\User\UserInterface' => $app['security.encoder.digest'], + )); + }); + + $app['security.encoder.digest'] = $app->share(function ($app) { + return new MessageDigestPasswordEncoder(); + }); + + $app['security.user_checker'] = $app->share(function ($app) { + return new UserChecker(); + }); + + $app['security.access_manager'] = $app->share(function ($app) { + return new AccessDecisionManager($app['security.voters']); + }); + + $app['security.voters'] = $app->share(function ($app) { + return array( + new RoleHierarchyVoter(new RoleHierarchy($app['security.role_hierarchy'])), + new AuthenticatedVoter($app['security.trust_resolver']), + ); + }); + + $app['security.firewall'] = $app->share(function ($app) { + return new Firewall($app['security.firewall_map'], $app['dispatcher']); + }); + + $app['security.channel_listener'] = $app->share(function ($app) { + return new ChannelListener( + $app['security.access_map'], + new RetryAuthenticationEntryPoint($app['request.http_port'], $app['request.https_port']), + $app['logger'] + ); + }); + + // generate the build-in authentication factories + foreach (array('logout', 'pre_auth', 'form', 'http', 'remember_me', 'anonymous') as $type) { + $entryPoint = null; + if ('http' === $type) { + $entryPoint = 'http'; + } elseif ('form' === $type) { + $entryPoint = 'form'; + } + + $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 = 'anonymous' === $type ? 'anonymous' : 'dao'; + if (!isset($app['security.authentication_provider.'.$name.'.'.$provider])) { + $app['security.authentication_provider.'.$name.'.'.$provider] = $app['security.authentication_provider.'.$provider.'._proto']($name); + } + + return array( + 'security.authentication_provider.'.$name.'.'.$provider, + 'security.authentication_listener.'.$name.'.'.$type, + $entryPoint ? 'security.entry_point.'.$name.'.'.$entryPoint : null, + $type, + ); + }); + } + + $app['security.firewall_map'] = $app->share(function ($app) { + $positions = array('logout', 'pre_auth', '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; + unset($firewall['pattern'], $firewall['users'], $firewall['security'], $firewall['stateless'], $firewall['context']); + + $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()); + } + $app['security.exception_listener.'.$name] = $app['security.exception_listener._proto']($entryPoint, $name); + } + } + + $configs[$name] = array($pattern, $listeners, $protected); + } + + $app['security.authentication_providers'] = array_map(function ($provider) use ($app) { + return $app[$provider]; + }, array_unique($providers)); + + $map = new FirewallMap(); + foreach ($configs as $name => $config) { + $map->add( + is_string($config[0]) ? new RequestMatcher($config[0]) : $config[0], + array_map(function ($listenerId) use ($app, $name) { + $listener = $app[$listenerId]; + + if (isset($app['security.remember_me.service.'.$name])) { + if ($listener instanceof AbstractAuthenticationListener) { + $listener->setRememberMeServices($app['security.remember_me.service.'.$name]); + } + if ($listener instanceof LogoutListener) { + $listener->addHandler($app['security.remember_me.service.'.$name]); + } + } + + return $listener; + }, $config[1]), + $config[2] ? $app['security.exception_listener.'.$name] : null + ); + } + + return $map; + }); + + $app['security.access_listener'] = $app->share(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'] = $app->share(function ($app) { + $map = new AccessMap(); + + foreach ($app['security.access_rules'] as $rule) { + if (is_string($rule[0])) { + $rule[0] = new RequestMatcher($rule[0]); + } + + $map->add($rule[0], (array) $rule[1], isset($rule[2]) ? $rule[2] : null); + } + + return $map; + }); + + $app['security.trust_resolver'] = $app->share(function ($app) { + return new AuthenticationTrustResolver('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken', 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken'); + }); + + $app['security.session_strategy'] = $app->share(function ($app) { + return new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE); + }); + + $app['security.http_utils'] = $app->share(function ($app) { + return new HttpUtils(isset($app['url_generator']) ? $app['url_generator'] : null, $app['url_matcher']); + }); + + $app['security.last_error'] = $app->protect(function (Request $request) { + if (class_exists('Symfony\Component\Security\Core\Security')) { + $error = Security::AUTHENTICATION_ERROR; + } else { + $error = SecurityContextInterface::AUTHENTICATION_ERROR; + } + if ($request->attributes->has($error)) { + return $request->attributes->get($error)->getMessage(); + } + + $session = $request->getSession(); + if ($session && $session->has($error)) { + $message = $session->get($error)->getMessage(); + $session->remove($error); + + return $message; + } + }); + + // prototypes (used by the Firewall Map) + + $app['security.context_listener._proto'] = $app->protect(function ($providerKey, $userProviders) use ($app) { + return $app->share(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 $app->share(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) use ($app) { + return $app->share(function () use ($app, $entryPoint, $name) { + return new ExceptionListener( + $app['security.token_storage'], + $app['security.trust_resolver'], + $app['security.http_utils'], + $name, + $app[$entryPoint], + null, // errorPage + null, // AccessDeniedHandlerInterface + $app['logger'] + ); + }); + }); + + $app['security.authentication.success_handler._proto'] = $app->protect(function ($name, $options) use ($app) { + return $app->share(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 $app->share(function () use ($name, $options, $app) { + return new DefaultAuthenticationFailureHandler( + $app, + $app['security.http_utils'], + $options, + $app['logger'] + ); + }); + }); + + $app['security.authentication_listener.form._proto'] = $app->protect(function ($name, $options) use ($app, $that) { + return $app->share(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['form.csrf_provider']) ? $app['form.csrf_provider'] : null + ); + }); + }); + + $app['security.authentication_listener.http._proto'] = $app->protect(function ($providerKey, $options) use ($app) { + return $app->share(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 $app->share(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 $app->share(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 $app->share(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['form.csrf_provider']) ? $app['form.csrf_provider'] : 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 $app->share(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 $app->share(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 $app->share(function () use ($app, $name, $options) { + return new BasicAuthenticationEntryPoint(isset($options['real_name']) ? $options['real_name'] : 'Secured'); + }); + }); + + $app['security.authentication_provider.dao._proto'] = $app->protect(function ($name) use ($app) { + return $app->share(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.anonymous._proto'] = $app->protect(function ($name) use ($app) { + return $app->share(function () use ($app, $name) { + return new AnonymousAuthenticationProvider($name); + }); + }); + + if (isset($app['validator'])) { + $app['security.validator.user_password_validator'] = $app->share(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 boot(Application $app) + { + $app['dispatcher']->addSubscriber($app['security.firewall']); + + foreach ($this->fakeRoutes as $route) { + list($method, $pattern, $name) = $route; + + $app->$method($pattern)->run(null)->bind($name); + } + } + + 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..3ba24a81 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SerializerServiceProvider.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; + +use Silex\Application; +use Silex\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}. + * + * @param Application $app + */ + public function register(Application $app) + { + $app['serializer'] = $app->share(function () use ($app) { + return new Serializer($app['serializer.normalizers'], $app['serializer.encoders']); + }); + + $app['serializer.encoders'] = $app->share(function () { + return array(new JsonEncoder(), new XmlEncoder()); + }); + + $app['serializer.normalizers'] = $app->share(function () { + return array(new CustomNormalizer(), new GetSetMethodNormalizer()); + }); + } + + /** + * {@inheritdoc} + * + * This provider does not execute any code when booting. + * + * @param Application $app + */ + public function boot(Application $app) + { + } +} 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..a35a0344 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ServiceControllerServiceProvider.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\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +use Silex\ServiceControllerResolver; + +class ServiceControllerServiceProvider implements ServiceProviderInterface +{ + public function register(Application $app) + { + $app['resolver'] = $app->share($app->extend('resolver', function ($resolver, $app) { + return new ServiceControllerResolver($resolver, $app['callback_resolver']); + })); + } + + public function boot(Application $app) + { + // noop + } +} 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..dcd94c02 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SessionServiceProvider.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 Silex\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +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; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; + +/** + * Symfony HttpFoundation component Provider for sessions. + * + * @author Fabien Potencier + */ +class SessionServiceProvider implements ServiceProviderInterface +{ + private $app; + + public function register(Application $app) + { + $this->app = $app; + + $app['session.test'] = false; + + $app['session'] = $app->share(function ($app) { + if (!isset($app['session.storage'])) { + if ($app['session.test']) { + $app['session.storage'] = $app['session.storage.test']; + } else { + $app['session.storage'] = $app['session.storage.native']; + } + } + + return new Session($app['session.storage']); + }); + + $app['session.storage.handler'] = $app->share(function ($app) { + return new NativeFileSessionHandler($app['session.storage.save_path']); + }); + + $app['session.storage.native'] = $app->share(function ($app) { + return new NativeSessionStorage( + $app['session.storage.options'], + $app['session.storage.handler'] + ); + }); + + $app['session.storage.test'] = $app->share(function () { + return new MockFileSessionStorage(); + }); + + $app['session.storage.options'] = array(); + $app['session.default_locale'] = 'en'; + $app['session.storage.save_path'] = null; + } + + public function onEarlyKernelRequest(GetResponseEvent $event) + { + $event->getRequest()->setSession($this->app['session']); + } + + public function onKernelRequest(GetResponseEvent $event) + { + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + return; + } + + // bootstrap the session + if (!isset($this->app['session'])) { + return; + } + + $session = $this->app['session']; + $cookies = $event->getRequest()->cookies; + + if ($cookies->has($session->getName())) { + $session->setId($cookies->get($session->getName())); + } else { + $session->migrate(false); + } + } + + public function onKernelResponse(FilterResponseEvent $event) + { + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + 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 function boot(Application $app) + { + $app['dispatcher']->addListener(KernelEvents::REQUEST, array($this, 'onEarlyKernelRequest'), 128); + + if ($app['session.test']) { + $app['dispatcher']->addListener(KernelEvents::REQUEST, array($this, 'onKernelRequest'), 192); + $app['dispatcher']->addListener(KernelEvents::RESPONSE, array($this, 'onKernelResponse'), -128); + } + } +} 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..0998c3a0 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/SwiftmailerServiceProvider.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 Silex\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; + +/** + * Swiftmailer Provider. + * + * @author Fabien Potencier + */ +class SwiftmailerServiceProvider implements ServiceProviderInterface +{ + public function register(Application $app) + { + $app['swiftmailer.options'] = array(); + $app['swiftmailer.use_spool'] = true; + + $app['mailer.initialized'] = false; + + $app['mailer'] = $app->share(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'] = $app->share(function ($app) { + return new \Swift_Transport_SpoolTransport($app['swiftmailer.transport.eventdispatcher'], $app['swiftmailer.spool']); + }); + + $app['swiftmailer.spool'] = $app->share(function ($app) { + return new \Swift_MemorySpool(); + }); + + $app['swiftmailer.transport'] = $app->share(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'] = $app->share(function () { + return new \Swift_Transport_StreamBuffer(new \Swift_StreamFilters_StringReplacementFilterFactory()); + }); + + $app['swiftmailer.transport.authhandler'] = $app->share(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'] = $app->share(function () { + return new \Swift_Events_SimpleEventDispatcher(); + }); + } + + public function boot(Application $app) + { + $app->finish(function () 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']); + } + }); + } +} 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..6b25d7f3 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/TranslationServiceProvider.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 Silex\Application; +use Silex\ServiceProviderInterface; +use Silex\Translator; +use Symfony\Component\Translation\MessageSelector; +use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Translation\Loader\XliffFileLoader; + +/** + * Symfony Translation component Provider. + * + * @author Fabien Potencier + */ +class TranslationServiceProvider implements ServiceProviderInterface +{ + public function register(Application $app) + { + $app['translator'] = $app->share(function ($app) { + $translator = new Translator($app, $app['translator.message_selector'], $app['translator.cache_dir'], $app['debug']); + + // Handle deprecated 'locale_fallback' + if (isset($app['locale_fallback'])) { + $app['locale_fallbacks'] = (array) $app['locale_fallback']; + } + + $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; + }); + + $app['translator.resources'] = function ($app) { + return array(); + }; + + $app['translator.message_selector'] = $app->share(function () { + return new MessageSelector(); + }); + + $app['translator.domains'] = array(); + $app['locale_fallbacks'] = array('en'); + $app['translator.cache_dir'] = null; + } + + public function boot(Application $app) + { + } +} 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..3b7e4324 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/TwigServiceProvider.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 Silex\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +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\HttpKernelExtension; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Bridge\Twig\Form\TwigRenderer; + +/** + * Twig integration for Silex. + * + * @author Fabien Potencier + */ +class TwigServiceProvider implements ServiceProviderInterface +{ + public function register(Application $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'] = $app->share(function ($app) { + $app['twig.options'] = array_replace( + array( + 'charset' => $app['charset'], + 'debug' => $app['debug'], + 'strict_variables' => $app['debug'], + ), $app['twig.options'] + ); + + $twig = new \Twig_Environment($app['twig.loader'], $app['twig.options']); + $twig->addGlobal('app', $app); + + if ($app['debug']) { + $twig->addExtension(new \Twig_Extension_Debug()); + } + + if (class_exists('Symfony\Bridge\Twig\Extension\RoutingExtension')) { + if (isset($app['url_generator'])) { + $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['form.factory'])) { + $app['twig.form.engine'] = $app->share(function ($app) { + return new TwigRendererEngine($app['twig.form.templates']); + }); + + $app['twig.form.renderer'] = $app->share(function ($app) { + return new TwigRenderer($app['twig.form.engine'], $app['form.csrf_provider']); + }); + + $twig->addExtension(new FormExtension($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)); + } + } + + return $twig; + }); + + $app['twig.loader.filesystem'] = $app->share(function ($app) { + return new \Twig_Loader_Filesystem($app['twig.path']); + }); + + $app['twig.loader.array'] = $app->share(function ($app) { + return new \Twig_Loader_Array($app['twig.templates']); + }); + + $app['twig.loader'] = $app->share(function ($app) { + return new \Twig_Loader_Chain(array( + $app['twig.loader.array'], + $app['twig.loader.filesystem'], + )); + }); + } + + public function boot(Application $app) + { + } +} diff --git a/vendor/silex/silex/src/Silex/Provider/UrlGeneratorServiceProvider.php b/vendor/silex/silex/src/Silex/Provider/UrlGeneratorServiceProvider.php new file mode 100644 index 00000000..ccbd2e7b --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/UrlGeneratorServiceProvider.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 Silex\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +use Symfony\Component\Routing\Generator\UrlGenerator; + +/** + * Symfony Routing component Provider for URL generation. + * + * @author Fabien Potencier + */ +class UrlGeneratorServiceProvider implements ServiceProviderInterface +{ + public function register(Application $app) + { + $app['url_generator'] = $app->share(function ($app) { + $app->flush(); + + return new UrlGenerator($app['routes'], $app['request_context']); + }); + } + + public function boot(Application $app) + { + } +} 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..cc2ce4ca --- /dev/null +++ b/vendor/silex/silex/src/Silex/Provider/ValidatorServiceProvider.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 Silex\Provider; + +use Silex\Application; +use Silex\ServiceProviderInterface; +use Silex\ConstraintValidatorFactory; +use Symfony\Component\Validator\Mapping\ClassMetadataFactory; +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(Application $app) + { + $app['validator'] = $app->share(function ($app) { + return $app['validator.builder']->getValidator(); + }); + + $app['validator.builder'] = $app->share(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'] = $app->share(function ($app) { + if (class_exists('Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory')) { + return new LazyLoadingMetadataFactory(new StaticMethodLoader()); + } + + return new ClassMetadataFactory(new StaticMethodLoader()); + }); + + $app['validator.validator_factory'] = $app->share(function () use ($app) { + return new ConstraintValidatorFactory($app, $app['validator.validator_service_ids']); + }); + + $app['validator.object_initializers'] = $app->share(function ($app) { + return array(); + }); + + $app['validator.validator_service_ids'] = array(); + } + + public function boot(Application $app) + { + } +} diff --git a/vendor/silex/silex/src/Silex/RedirectableUrlMatcher.php b/vendor/silex/silex/src/Silex/RedirectableUrlMatcher.php new file mode 100644 index 00000000..aa0cdf13 --- /dev/null +++ b/vendor/silex/silex/src/Silex/RedirectableUrlMatcher.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; + +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); }, + '_route' => null, + 'url' => $url, + ); + } +} diff --git a/vendor/silex/silex/src/Silex/Route.php b/vendor/silex/silex/src/Silex/Route.php new file mode 100644 index 00000000..1ade9b78 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Route.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 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; + } +} 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/ServiceProviderInterface.php b/vendor/silex/silex/src/Silex/ServiceProviderInterface.php new file mode 100644 index 00000000..cd040795 --- /dev/null +++ b/vendor/silex/silex/src/Silex/ServiceProviderInterface.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 Silex; + +/** + * Interface that all Silex service providers must implement. + * + * @author Fabien Potencier + */ +interface ServiceProviderInterface +{ + /** + * Registers services on the given app. + * + * This method should only be used to configure services and parameters. + * It should not get services. + */ + public function register(Application $app); + + /** + * 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). + */ + public function boot(Application $app); +} diff --git a/vendor/silex/silex/src/Silex/Translator.php b/vendor/silex/silex/src/Silex/Translator.php new file mode 100644 index 00000000..20f10472 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Translator.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; + +use Symfony\Component\Translation\Translator as BaseTranslator; +use Symfony\Component\Translation\MessageSelector; + +/** + * Translator that gets the current locale from the Silex application. + * + * @author Fabien Potencier + */ +class Translator extends BaseTranslator +{ + protected $app; + + public function __construct(Application $app, MessageSelector $selector, $cacheDir = null, $debug = false) + { + $this->app = $app; + + parent::__construct(null, $selector, $cacheDir, $debug); + } + + public function getLocale() + { + return $this->app['locale']; + } + + public function setLocale($locale) + { + if (null === $locale) { + return; + } + + $this->app['locale'] = $locale; + + parent::setLocale($locale); + } +} diff --git a/vendor/silex/silex/src/Silex/Util/Compiler.php b/vendor/silex/silex/src/Silex/Util/Compiler.php new file mode 100644 index 00000000..2d2930d4 --- /dev/null +++ b/vendor/silex/silex/src/Silex/Util/Compiler.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 Silex\Util; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Process\Process; + +/** + * The Compiler class compiles the Silex framework. + * + * This is deprecated. Use composer instead. + * + * @author Fabien Potencier + */ +class Compiler +{ + protected $version; + + /** + * Compiles the Silex source code into one single Phar file. + * + * @param string $pharFile Name of the output Phar file + */ + public function compile($pharFile = 'silex.phar') + { + if (file_exists($pharFile)) { + unlink($pharFile); + } + + $process = new Process('git log --pretty="%h %ci" -n1 HEAD'); + if ($process->run() > 0) { + throw new \RuntimeException('The git binary cannot be found.'); + } + $this->version = trim($process->getOutput()); + + $phar = new \Phar($pharFile, 0, 'silex.phar'); + $phar->setSignatureAlgorithm(\Phar::SHA1); + + $phar->startBuffering(); + + $root = __DIR__.'/../../..'; + + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->name('*.php') + ->notName('Compiler.php') + ->exclude('Tests') + ->in($root.'/src') + ->in($root.'/vendor/pimple/pimple/lib') + ->in($root.'/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher') + ->in($root.'/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation') + ->in($root.'/vendor/symfony/http-kernel/Symfony/Component/HttpKernel') + ->in($root.'/vendor/symfony/routing/Symfony/Component/Routing') + ->in($root.'/vendor/symfony/browser-kit/Symfony/Component/BrowserKit') + ->in($root.'/vendor/symfony/css-selector/Symfony/Component/CssSelector') + ->in($root.'/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler') + ; + + foreach ($finder as $file) { + $this->addFile($phar, $file); + } + + $this->addFile($phar, new \SplFileInfo($root.'/LICENSE'), false); + $this->addFile($phar, new \SplFileInfo($root.'/vendor/autoload.php')); + $this->addFile($phar, new \SplFileInfo($root.'/vendor/composer/ClassLoader.php')); + $this->addFile($phar, new \SplFileInfo($root.'/vendor/composer/autoload_namespaces.php')); + $this->addFile($phar, new \SplFileInfo($root.'/vendor/composer/autoload_classmap.php')); + + // Stubs + $phar->setStub($this->getStub()); + + $phar->stopBuffering(); + + unset($phar); + } + + protected function addFile(\Phar $phar, \SplFileInfo $file, $strip = true) + { + $path = str_replace(dirname(dirname(dirname(__DIR__))).DIRECTORY_SEPARATOR, '', $file->getRealPath()); + + $content = file_get_contents($file); + if ($strip) { + $content = self::stripWhitespace($content); + } + + $content = preg_replace("/const VERSION = '.*?';/", "const VERSION = '".$this->version."';", $content); + + $phar->addFromString($path, $content); + } + + protected function getStub() + { + return <<<'EOF' + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +Phar::mapPhar('silex.phar'); + +require_once 'phar://silex.phar/vendor/autoload.php'; + +if ('cli' === php_sapi_name() && basename(__FILE__) === basename($_SERVER['argv'][0]) && isset($_SERVER['argv'][1])) { + switch ($_SERVER['argv'][1]) { + case 'update': + $remoteFilename = 'http://silex.sensiolabs.org/get/silex.phar'; + $localFilename = __DIR__.'/silex.phar'; + + file_put_contents($localFilename, file_get_contents($remoteFilename)); + break; + + case 'check': + $latest = trim(file_get_contents('http://silex.sensiolabs.org/get/version')); + + if ($latest != Silex\Application::VERSION) { + printf("A newer Silex version is available (%s).\n", $latest); + } else { + print("You are using the latest Silex version.\n"); + } + break; + + case 'version': + printf("Silex version %s\n", Silex\Application::VERSION); + break; + + default: + printf("Unknown command '%s' (available commands: version, check, and update).\n", $_SERVER['argv'][1]); + } + + exit(0); +} + +__HALT_COMPILER(); +EOF; + } + + /** + * Removes whitespace from a PHP source string while preserving line numbers. + * + * Based on Kernel::stripComments(), but keeps line numbers intact. + * + * @param string $source A PHP string + * + * @return string The PHP string with the whitespace removed + */ + public static function stripWhitespace($source) + { + if (!function_exists('token_get_all')) { + return $source; + } + + $output = ''; + foreach (token_get_all($source) as $token) { + if (is_string($token)) { + $output .= $token; + } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { + $output .= str_repeat("\n", substr_count($token[1], "\n")); + } elseif (T_WHITESPACE === $token[0]) { + // reduce wide spaces + $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); + // normalize newlines to \n + $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); + // trim leading spaces + $whitespace = preg_replace('{\n +}', "\n", $whitespace); + $output .= $whitespace; + } else { + $output .= $token[1]; + } + } + + return $output; + } +} 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..576ad1a7 --- /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(). + */ + public 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..a0f68b46 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/FormTraitTest.php @@ -0,0 +1,37 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\FormServiceProvider; + +/** + * FormTrait test cases. + * + * @author Fabien Potencier + * + * @requires PHP 5.4 + */ +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..9d46cc52 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/MonologTraitTest.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\Application; + +use Silex\Provider\MonologServiceProvider; +use Monolog\Handler\TestHandler; +use Monolog\Logger; + +/** + * MonologTrait test cases. + * + * @author Fabien Potencier + * + * @requires PHP 5.4 + */ +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' => $app->share(function () use ($app) { + return new TestHandler($app['monolog.level']); + }), + )); + + 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..6a3cddb3 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SecurityTraitTest.php @@ -0,0 +1,129 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\SecurityServiceProvider; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityTrait test cases. + * + * @author Fabien Potencier + * + * @requires PHP 5.4 + */ +class SecurityTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testUser() + { + $request = Request::create('/'); + + $app = $this->createApplication(array( + 'fabien' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + )); + $app->get('/', function () { return 'foo'; }); + $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() + { + $request = Request::create('/'); + + $app = $this->createApplication(); + $app->get('/', function () { return 'foo'; }); + $app->handle($request); + $this->assertNull($app->user()); + } + + public function testUserWithInvalidUser() + { + $request = Request::create('/'); + + $app = $this->createApplication(); + $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 testEncodePassword() + { + $app = $this->createApplication(array( + 'fabien' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + )); + + $user = new User('foo', 'bar'); + $this->assertEquals('5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==', $app->encodePassword($user, 'foo')); + } + + /** + * @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', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + 'monique' => array('ROLE_USER', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + )); + $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..a37725e9 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/SwiftmailerTraitTest.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\SwiftmailerServiceProvider; + +/** + * SwiftmailerTrait test cases. + * + * @author Fabien Potencier + * + * @requires PHP 5.4 + */ +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..af2d8a41 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TranslationTraitTest.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\Application; + +use Silex\Provider\TranslationServiceProvider; + +/** + * TranslationTrait test cases. + * + * @author Fabien Potencier + * + * @requires PHP 5.4 + */ +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..70710cd5 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/TwigTraitTest.php @@ -0,0 +1,82 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\TwigServiceProvider; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * TwigTrait test cases. + * + * @author Fabien Potencier + * + * @requires PHP 5.4 + */ +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..b365cf1a --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Application/UrlGeneratorTraitTest.php @@ -0,0 +1,49 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Application; + +use Silex\Provider\UrlGeneratorServiceProvider; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * UrlGeneratorTrait test cases. + * + * @author Fabien Potencier + * + * @requires PHP 5.4 + */ +class UrlGeneratorTraitTest extends \PHPUnit_Framework_TestCase +{ + public function testUrl() + { + $app = $this->createApplication(); + $app['url_generator'] = $translator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->disableOriginalConstructor()->getMock(); + $translator->expects($this->once())->method('generate')->with('foo', array(), UrlGeneratorInterface::ABSOLUTE_URL); + $app->url('foo'); + } + + public function testPath() + { + $app = $this->createApplication(); + $app['url_generator'] = $translator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->disableOriginalConstructor()->getMock(); + $translator->expects($this->once())->method('generate')->with('foo', array(), UrlGeneratorInterface::ABSOLUTE_PATH); + $app->path('foo'); + } + + public function createApplication() + { + $app = new UrlGeneratorApplication(); + $app->register(new UrlGeneratorServiceProvider()); + + return $app; + } +} 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..fa018e13 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ApplicationTest.php @@ -0,0 +1,699 @@ + + * + * 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\ControllerCollection; +use Silex\ControllerProviderInterface; +use Silex\Route; +use Silex\Provider\MonologServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\EventDispatcher\Event; + +/** + * 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(); + + $app->get('/{name}', 'Silex\Tests\FooController::barAction'); + + $this->assertEquals('Hello Fabien', $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); + } + + /** + * @expectedException \RuntimeException + */ + public function testAccessingRequestOutsideOfScopeShouldThrowRuntimeException() + { + $app = new Application(); + + $request = $app['request']; + } + + /** + * @expectedException \RuntimeException + */ + public function testAccessingRequestOutsideOfScopeShouldThrowRuntimeExceptionAfterHandling() + { + $app = new Application(); + $app->get('/', function () { + return 'hello'; + }); + $app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false); + + $request = $app['request']; + } + + 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 testSubRequestDoesNotReplaceMainRequestAfterHandling() + { + $mainRequest = Request::create('/'); + $subRequest = Request::create('/sub'); + + $app = new Application(); + $app->get('/sub', function (Request $request) { + return new Response('foo'); + }); + $app->get('/', function (Request $request) use ($subRequest, $app) { + $response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + + // request in app must be the main request here + $response->setContent($response->getContent().' '.$app['request']->getPathInfo()); + + return $response; + }); + + $this->assertEquals('foo /', $app->handle($mainRequest)->getContent()); + } + + public function testRegisterShouldReturnSelf() + { + $app = new Application(); + $provider = $this->getMockBuilder('Silex\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" or a "ControllerProviderInterface" instance. + */ + 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 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 testRedirectDoesNotRaisePHPNoticesWhenMonologIsRegistered() + { + $app = new Application(); + + ErrorHandler::register(null, false); + $app['monolog.logfile'] = 'php://memory'; + $app->register(new MonologServiceProvider()); + $app->get('/foo/', function () { return 'ok'; }); + + $response = $app->handle(Request::create('/foo')); + $this->assertEquals(301, $response->getStatusCode()); + } + + 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()); + } + + /** + * @requires PHP 5.4 + */ + 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()); + } +} + +class FooController +{ + public function barAction(Application $app, $name) + { + return 'Hello '.$app->escape($name); + } +} + +class IncorrectControllerCollection implements ControllerProviderInterface +{ + public function connect(Application $app) + { + return; + } +} 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..7eebbe66 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/CallbackResolverTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Silex\Tests; + +use Silex\CallbackResolver; + +class CallbackResolverTest extends \PHPUnit_Framework_Testcase +{ + private $app; + private $resolver; + + public function setup() + { + $this->app = new \Pimple(); + $this->resolver = new CallbackResolver($this->app); + } + + public function testShouldResolveCallback() + { + $this->app['some_service'] = function () { return new \stdClass(); }; + + $this->assertTrue($this->resolver->isValid('some_service:methodName')); + $this->assertEquals( + array($this->app['some_service'], 'methodName'), + $this->resolver->convertCallback('some_service:methodName') + ); + } + + public function testNonStringsAreNotValid() + { + $this->assertFalse($this->resolver->isValid(null)); + $this->assertFalse($this->resolver->isValid('some_service::methodName')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Service "some_service" does not exist. + */ + public function testShouldThrowAnExceptionIfServiceIsMissing() + { + $this->resolver->convertCallback('some_service:methodName'); + } +} 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..5e911304 --- /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'] = $app->share(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..9d3a7a08 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerCollectionTest.php @@ -0,0 +1,224 @@ + + * + * 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\ControllerCollection; +use Silex\Exception\ControllerFrozenException; +use Silex\Route; + +/** + * 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_')); + + $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 () {}); + + $routes = $controllers->flush(); + + $this->assertCount(2, $routes->all()); + $this->assertEquals(array('_a_a', '_a_a_'), 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_'), 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_'), array_keys($routes->all())); + } + + 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 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(); + } +} + +class MyRoute1 extends Route +{ + public $foo; + + public function foo($value) + { + $this->foo = $value; + } +} 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..120aad57 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ControllerResolverTest.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; + +use Silex\ControllerResolver; +use Silex\Application; +use Symfony\Component\HttpFoundation\Request; + +/** + * ControllerResolver test cases. + * + * @author Fabien Potencier + */ +class ControllerResolverTest extends \PHPUnit_Framework_TestCase +{ + 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..06b225b8 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/EventListener/LogListenerTest.php @@ -0,0 +1,94 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\EventListener; + +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('info') + ->with($this->equalTo('> 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('info') + ->with($this->equalTo('< 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->once()) + ->method('critical') + ->with($this->equalTo('RuntimeException: Fatal error (uncaught exception) at '.__FILE__.' line '.(__LINE__ + 14))) + ; + + $logger + ->expects($this->once()) + ->method('error') + ->with($this->equalTo('Symfony\Component\HttpKernel\Exception\HttpException: Http error (uncaught exception) at '.__FILE__.' line '.(__LINE__ + 10))) + ; + + $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..4018695a --- /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(); + $app['exception_handler']->disable(); + + $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/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..654d0fd8 --- /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['dispatcher'] = $app->share($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/LazyUrlMatcherTest.php b/vendor/silex/silex/tests/Silex/Tests/LazyUrlMatcherTest.php new file mode 100644 index 00000000..6a4f46ad --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LazyUrlMatcherTest.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\LazyUrlMatcher; + +/** + * LazyUrlMatcher test case. + * + * @author Leszek Prabucki + */ +class LazyUrlMatcherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Silex\LazyUrlMatcher::getUrlMatcher + */ + public function testUserMatcherIsCreatedLazily() + { + $callCounter = 0; + $urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock(); + + $matcher = new LazyUrlMatcher(function () use ($urlMatcher, &$callCounter) { + ++$callCounter; + + return $urlMatcher; + }); + + $this->assertEquals(0, $callCounter); + $matcher->match('path'); + $this->assertEquals(1, $callCounter); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Factory supplied to LazyUrlMatcher must return implementation of UrlMatcherInterface. + */ + public function testThatCanInjectUrlMatcherOnly() + { + $matcher = new LazyUrlMatcher(function () { + return 'someMatcher'; + }); + + $matcher->match('path'); + } + + /** + * @covers Silex\LazyUrlMatcher::match + */ + public function testMatchIsProxy() + { + $urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock(); + $urlMatcher->expects($this->once()) + ->method('match') + ->with('path') + ->will($this->returnValue('matcherReturnValue')); + + $matcher = new LazyUrlMatcher(function () use ($urlMatcher) { + return $urlMatcher; + }); + $result = $matcher->match('path'); + + $this->assertEquals('matcherReturnValue', $result); + } + + /** + * @covers Silex\LazyUrlMatcher::setContext + */ + public function testSetContextIsProxy() + { + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext')->getMock(); + $urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock(); + $urlMatcher->expects($this->once()) + ->method('setContext') + ->with($context); + + $matcher = new LazyUrlMatcher(function () use ($urlMatcher) { + return $urlMatcher; + }); + $matcher->setContext($context); + } + + /** + * @covers Silex\LazyUrlMatcher::getContext + */ + public function testGetContextIsProxy() + { + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext')->getMock(); + $urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock(); + $urlMatcher->expects($this->once()) + ->method('getContext') + ->will($this->returnValue($context)); + + $matcher = new LazyUrlMatcher(function () use ($urlMatcher) { + return $urlMatcher; + }); + $resultContext = $matcher->getContext(); + + $this->assertSame($resultContext, $context); + } +} 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..483ea264 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/LocaleTest.php @@ -0,0 +1,76 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests; + +use Silex\Application; +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->get('/', function (Request $request) { return $request->getLocale(); }); + $response = $app->handle(Request::create('/')); + $this->assertEquals('en', $response->getContent()); + + $app = new Application(); + $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->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->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->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->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..9e71edf6 --- /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 () use ($app) { + $app['project'] = $app['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/DoctrineServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php new file mode 100644 index 00000000..e6e1d753 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/DoctrineServiceProviderTest.php @@ -0,0 +1,81 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +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']); + } +} 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..76df0559 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/FormServiceProviderTest.php @@ -0,0 +1,235 @@ + + * + * 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\TranslationServiceProvider; +use Silex\Provider\ValidatorServiceProvider; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +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; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; + +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 testFormServiceProviderWillLoadTypes() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['form.types'] = $app->share($app->extend('form.types', function ($extensions) { + $extensions[] = new DummyFormType(); + + return $extensions; + })); + + $form = $app['form.factory']->createBuilder(class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType') ? 'Symfony\Component\Form\Extension\Core\Type\FormType' : 'form', array()) + ->add('dummy', class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType') ? 'Silex\Tests\Provider\DummyFormType' : 'dummy') + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + public function testFormServiceProviderWillLoadTypeExtensions() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['form.type.extensions'] = $app->share($app->extend('form.type.extensions', function ($extensions) { + $extensions[] = new DummyFormTypeExtension(); + + return $extensions; + })); + + $form = $app['form.factory']->createBuilder(class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType') ? 'Symfony\Component\Form\Extension\Core\Type\FormType' : 'form', array()) + ->add('file', class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType') ? 'Symfony\Component\Form\Extension\Core\Type\FileType' : 'file', array('image_path' => 'webPath')) + ->getForm(); + + $this->assertInstanceOf('Symfony\Component\Form\Form', $form); + } + + public function testFormServiceProviderWillLoadTypeGuessers() + { + $app = new Application(); + + $app->register(new FormServiceProvider()); + + $app['form.type.guessers'] = $app->share($app->extend('form.type.guessers', function ($guessers) { + $guessers[] = new FormTypeGuesserChain(array()); + + return $guessers; + })); + + $this->assertInstanceOf('Symfony\Component\Form\FormFactory', $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['form.csrf_provider'] = $app->share(function () { + return new FakeCsrfProvider(); + }); + + $form = $app['form.factory']->createBuilder(class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType') ? 'Symfony\Component\Form\Extension\Core\Type\FormType' : 'form', 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'); + } + } +} + +if (class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType')) { + class DummyFormType extends AbstractType + { + } +} else { + 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 class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType') ? 'Symfony\Component\Form\Extension\Core\Type\FileType' : 'file'; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefined(array('image_path')); + } + } +} else { + class DummyFormTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType') ? 'Symfony\Component\Form\Extension\Core\Type\FileType' : 'file'; + } + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + if (!method_exists($resolver, 'setDefined')) { + $resolver->setOptional(array('image_path')); + } else { + $resolver->setDefined(array('image_path')); + } + } + } +} + +if (!class_exists('Symfony\Component\Form\Extension\DataCollector\DataCollectorExtension')) { + // Symfony 2.3 only + class FakeCsrfProvider implements CsrfProviderInterface + { + public function generateCsrfToken($intention) + { + return $intention.'123'; + } + + public function isCsrfTokenValid($intention, $token) + { + return $token === $this->generateCsrfToken($intention); + } + } +} else { + class FakeCsrfProvider implements CsrfTokenManagerInterface + { + public function getToken($tokenId) + { + return new CsrfToken($tokenId, '123'); + } + + public function refreshToken($tokenId) + { + return new CsrfToken($tokenId, '123'); + } + + public function removeToken($tokenId) + { + } + + public function isTokenValid(CsrfToken $token) + { + return '123' === $token->getValue(); + } + } +} 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..6873df2b --- /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\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..deec1165 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/HttpFragmentServiceProviderTest.php @@ -0,0 +1,51 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace 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() + { + if (!class_exists('Symfony\Component\HttpFoundation\RequestStack')) { + $this->markTestSkipped('HttpFragmentServiceProvider is not available on Symfony <2.4'); + } + + $app = new Application(); + + $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..888ce8c5 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/MonologServiceProviderTest.php @@ -0,0 +1,211 @@ + + * + * 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\Handler\TestHandler; +use Monolog\Logger; +use Silex\Application; +use Silex\Provider\MonologServiceProvider; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; + +/** + * 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']->hasInfo('> GET /foo')); + $this->assertTrue($app['monolog.handler']->hasInfo('< 200')); + + $records = $app['monolog.handler']->getRecords(); + $this->assertContains('Matched route "GET_foo"', $records[0]['message']); + } + + 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 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); + + $records = $app['monolog.handler']->getRecords(); + $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']->hasInfo('< 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'); + } + + protected function assertMatchingRecord($pattern, $level, $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()); + + $app['monolog.handler'] = $app->share(function () use ($app) { + $level = MonologServiceProvider::translateLevel($app['monolog.level']); + + return new TestHandler($level); + }); + + 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..04223770 --- /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', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + ), + ), + ); + + $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/SecurityServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php new file mode 100644 index 00000000..c67b2e73 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SecurityServiceProviderTest.php @@ -0,0 +1,292 @@ + + * + * 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\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 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 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' => true, + 'logout' => true, + 'users' => array( + // password is foo + 'fabien' => array('ROLE_USER', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + 'admin' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + ), + ), + ), + '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', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + 'admin' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + ), + ), + ), + '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; + } +} 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..ff213285 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SessionServiceProviderTest.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\SessionServiceProvider; +use Symfony\Component\HttpKernel\Client; + +/** + * 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()); + } +} 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..006fc066 --- /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[] = $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..0264aa58 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/SwiftmailerServiceProviderTest.php @@ -0,0 +1,92 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace 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'] = $app->share(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'] = $app->share(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); + } +} 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..60838fff --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/TranslationServiceProviderTest.php @@ -0,0 +1,150 @@ + + * + * 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; + +/** + * TranslationProvider test cases. + * + * @author Daniel Tschinder + */ +class TranslationServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @return Application + */ + protected function getPreparedApp() + { + $app = new Application(); + + $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 testBackwardCompatiblityForFallback() + { + $app = $this->getPreparedApp(); + $app['locale_fallback'] = 'de'; + + $result = $app['translator']->trans('key1', array(), null, 'ru'); + $this->assertEquals('The german translation', $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 testChangingLocale() + { + $app = $this->getPreparedApp(); + + $this->assertEquals('The translation', $app['translator']->trans('key1')); + + $app['locale'] = 'de'; + + $this->assertEquals('The german translation', $app['translator']->trans('key1')); + } + + public function testChangingLocaleViaTranslator() + { + $app = $this->getPreparedApp(); + + $this->assertEquals('The translation', $app['translator']->trans('key1')); + + $app['translator']->setLocale('de'); + + $this->assertEquals('The german translation', $app['translator']->trans('key1')); + $this->assertEquals('de', $app['locale']); + } +} 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..27645997 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/TwigServiceProviderTest.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\Provider; + +use Silex\Application; +use Silex\Provider\TwigServiceProvider; +use Silex\Provider\HttpFragmentServiceProvider; +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 testRenderFunction() + { + if (!class_exists('Symfony\Component\HttpFoundation\RequestStack')) { + $this->markTestSkipped(); + } + + $app = new Application(); + + $app->register(new HttpFragmentServiceProvider()); + $app->register(new TwigServiceProvider(), array( + 'twig.templates' => array( + 'hello' => '{{ render("/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'); + }); + + $request = Request::create('/hello'); + $response = $app->handle($request); + $this->assertEquals('foo', $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'] = $app->share(function ($app) use ($loader) { + return $loader; + }); + $this->assertEquals('foo', $app['twig.loader']->getSourceContext('foo')->getCode()); + } +} diff --git a/vendor/silex/silex/tests/Silex/Tests/Provider/UrlGeneratorServiceProviderTest.php b/vendor/silex/silex/tests/Silex/Tests/Provider/UrlGeneratorServiceProviderTest.php new file mode 100644 index 00000000..3ab85a83 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/UrlGeneratorServiceProviderTest.php @@ -0,0 +1,120 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Provider; + +use Silex\Application; +use Silex\Provider\UrlGeneratorServiceProvider; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * UrlGeneratorProvider test cases. + * + * @author Igor Wiedler + */ +class UrlGeneratorServiceProviderTest extends \PHPUnit_Framework_TestCase +{ + public function testRegister() + { + $app = new Application(); + + $app->register(new UrlGeneratorServiceProvider()); + + $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->register(new UrlGeneratorServiceProvider()); + + $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->register(new UrlGeneratorServiceProvider()); + + $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->register(new UrlGeneratorServiceProvider()); + + $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->register(new UrlGeneratorServiceProvider()); + + $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()); + } +} 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..642b8d29 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Provider/ValidatorServiceProviderTest.php @@ -0,0 +1,202 @@ + + * + * 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()); + + return $app; + } + + public function testRegisterWithCustomValidators() + { + $app = new Application(); + + $app['custom.validator'] = $app->share(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\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) + { + if (!class_exists('Symfony\Component\Validator\Constraints\Expression')) { + $this->markTestSkipped('Expression are not supported by this version of Symfony'); + } + + $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) + { + $app->register(new ValidatorServiceProvider()); + $app->register(new FormServiceProvider()); + + $constraints = new Assert\Collection(array( + 'email' => array( + new Assert\NotBlank(), + new Assert\Email(), + ), + )); + + $builder = $app['form.factory']->createBuilder(class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType') ? 'Symfony\Component\Form\Extension\Core\Type\FormType' : 'form', array(), array( + 'constraints' => $constraints, + 'csrf_protection' => false, + )); + + $form = $builder + ->add('email', class_exists('Symfony\Component\Form\Extension\Core\Type\RangeType') ? 'Symfony\Component\Form\Extension\Core\Type\EmailType' : 'email', 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->share($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->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..8f03f1d2 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/Route/SecurityTraitTest.php @@ -0,0 +1,87 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Silex\Tests\Route; + +use Silex\Application; +use Silex\Provider\SecurityServiceProvider; +use Symfony\Component\HttpFoundation\Request; + +/** + * SecurityTrait test cases. + * + * @author Fabien Potencier + * + * @requires PHP 5.4 + */ +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', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='), + ), + ), + ), + )); + + 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..6c1f9584 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/RouterTest.php @@ -0,0 +1,272 @@ + + * + * 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 () use ($app) { + return new Response($app['request']->getRequestUri()); + }); + + $app->error(function ($e) use ($app) { + return new Response($app['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 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..d44c432e --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/ServiceControllerResolverRouterTest.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; + +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['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) + { + $app->register(new ServiceControllerServiceProvider()); + + $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..58d347f2 --- /dev/null +++ b/vendor/silex/silex/tests/Silex/Tests/WebTestCaseTest.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 Silex\Application; +use Silex\WebTestCase; + +/** + * 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 () use ($app) { + $user = $app['request']->server->get('PHP_AUTH_USER'); + $pass = $app['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..8feda35d --- /dev/null +++ b/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -0,0 +1,32 @@ +CHANGELOG +========= + +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..5982b85f --- /dev/null +++ b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace 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 + */ +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; + } + + /** + * Adds a service as event listener. + * + * @param string $eventName Event for which the listener is added + * @param array $callback The service ID of the listener service & the method + * name that has to be called + * @param int $priority The higher this value, the earlier an event listener + * will be triggered in the chain. + * Defaults to 0. + * + * @throws \InvalidArgumentException + */ + public function addListenerService($eventName, $callback, $priority = 0) + { + if (!is_array($callback) || 2 !== count($callback)) { + throw new \InvalidArgumentException('Expected an array("service", "method") argument'); + } + + $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); + } + + public function removeListener($eventName, $listener) + { + $this->lazyLoad($eventName); + + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $i => 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 (bool) count($this->listenerIds) || (bool) count($this->listeners); + } + + if (isset($this->listenerIds[$eventName])) { + return true; + } + + return parent::hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null === $eventName) { + foreach ($this->listenerIds as $serviceEventName => $args) { + $this->lazyLoad($serviceEventName); + } + } else { + $this->lazyLoad($eventName); + } + + return parent::getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + $this->lazyLoad($eventName); + + return parent::getListenerPriority($eventName, $listener); + } + + /** + * Adds a service as event subscriber. + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name (which must implement EventSubscriberInterface) + */ + public function addSubscriberService($serviceId, $class) + { + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } elseif (is_string($params[0])) { + $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + public function getContainer() + { + return $this->container; + } + + /** + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + */ + protected function lazyLoad($eventName) + { + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as 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..e729eeb1 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,372 @@ + + * + * 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) + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + if (null !== $this->logger && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + + $this->preProcess($eventName); + $this->preDispatch($eventName, $event); + + $e = $this->stopwatch->start($eventName, 'section'); + + $this->dispatcher->dispatch($eventName, $event); + + if ($e->isStarted()) { + $e->stop(); + } + + $this->postDispatch($eventName, $event); + $this->postProcess($eventName); + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getCalledListeners() + { + $called = array(); + foreach ($this->called as $eventName => $listeners) { + foreach ($listeners as $listener) { + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + $called[$eventName.'.'.$info['pretty']] = $info; + } + } + + return $called; + } + + /** + * {@inheritdoc} + */ + public function getNotCalledListeners() + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e)); + } + + // unable to retrieve the uncalled listeners + return array(); + } + + $notCalled = array(); + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + $called = false; + if (isset($this->called[$eventName])) { + foreach ($this->called[$eventName] as $l) { + if ($l->getWrappedListener() === $listener) { + $called = true; + + break; + } + } + } + + if (!$called) { + $info = $this->getListenerInfo($listener, $eventName); + $notCalled[$eventName.'.'.$info['pretty']] = $info; + } + } + } + + uasort($notCalled, array($this, 'sortListenersByPriority')); + + return $notCalled; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return call_user_func_array(array($this->dispatcher, $method), $arguments); + } + + /** + * Called before dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function preDispatch($eventName, Event $event) + { + } + + /** + * Called after dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function postDispatch($eventName, Event $event) + { + } + + private function preProcess($eventName) + { + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $info = $this->getListenerInfo($listener, $eventName); + $name = isset($info['class']) ? $info['class'] : $info['type']; + $wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $wrappedListener, $info['priority']); + } + } + + private function postProcess($eventName) + { + unset($this->wrappedListeners[$eventName]); + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. + continue; + } + // Unwrap listener + $priority = $this->getListenerPriority($eventName, $listener); + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); + + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty'])); + } + + if (!isset($this->called[$eventName])) { + $this->called[$eventName] = new \SplObjectStorage(); + } + + $this->called[$eventName]->attach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName)); + } + + if ($listener->stoppedPropagation()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName)); + } + + $skipped = true; + } + } + } + + /** + * Returns information about the listener. + * + * @param object $listener The listener + * @param string $eventName The event name + * + * @return array Information about the listener + */ + private function getListenerInfo($listener, $eventName) + { + $info = array( + 'event' => $eventName, + 'priority' => $this->getListenerPriority($eventName, $listener), + ); + if ($listener instanceof \Closure) { + $info += array( + 'type' => 'Closure', + 'pretty' => 'closure', + ); + } elseif (is_string($listener)) { + try { + $r = new \ReflectionFunction($listener); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Function', + 'function' => $listener, + 'file' => $file, + 'line' => $line, + 'pretty' => $listener, + ); + } elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) { + if (!is_array($listener)) { + $listener = array($listener, '__invoke'); + } + $class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0]; + try { + $r = new \ReflectionMethod($class, $listener[1]); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Method', + 'class' => $class, + 'method' => $listener[1], + 'file' => $file, + 'line' => $line, + 'pretty' => $class.'::'.$listener[1], + ); + } + + return $info; + } + + private function sortListenersByPriority($a, $b) + { + if (is_int($a['priority']) && !is_int($b['priority'])) { + return 1; + } + + if (!is_int($a['priority']) && is_int($b['priority'])) { + return -1; + } + + if ($a['priority'] === $b['priority']) { + return 0; + } + + if ($a['priority'] > $b['priority']) { + return -1; + } + + return 1; + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php new file mode 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..e16627d6 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.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\EventDispatcher\Debug; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Fabien Potencier + */ +class WrappedListener +{ + private $listener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + + public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->name = $name; + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->called = false; + $this->stoppedPropagation = false; + } + + public function getWrappedListener() + { + return $this->listener; + } + + public function wasCalled() + { + return $this->called; + } + + public function stoppedPropagation() + { + return $this->stoppedPropagation; + } + + public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) + { + $this->called = true; + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 00000000..326bfd18 --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.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\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * 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) as $id => $events) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id)); + } + + foreach ($events as $event) { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) { + throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback(array( + '/(?<=\b)[a-z]/i', + '/[^a-z0-9]/i', + ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + } + + $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + } + } + + foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id)); + } + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getParameterBag()->resolveValue($def->getClass()); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + + if (!is_subclass_of($class, $interface)) { + if (!class_exists($class, false)) { + throw new \InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} diff --git a/vendor/symfony/event-dispatcher/Event.php b/vendor/symfony/event-dispatcher/Event.php new file mode 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..8cd5692c --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcher.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\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 + */ +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 (!isset($this->listeners[$eventName])) { + return array(); + } + + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach ($this->listeners as $eventName => $eventListeners) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return array_filter($this->sorted); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + return $priority; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return (bool) count($this->getListeners($eventName)); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName]); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]); + } + } + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->addListener($eventName, array($subscriber, $params)); + } elseif (is_string($params[0])) { + $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_array($params) && is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, array($subscriber, $listener[0])); + } + } else { + $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0])); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param callable[] $listeners The event listeners + * @param string $eventName The name of the event to dispatch + * @param Event $event The event object to pass to the event handlers/listeners + */ + protected function doDispatch($listeners, $eventName, Event $event) + { + foreach ($listeners as $listener) { + if ($event->isPropagationStopped()) { + break; + } + call_user_func($listener, $event, $eventName, $this); + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + * + * @param string $eventName The name of the event + */ + private function sortListeners($eventName) + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]); + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 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..2b9f40e2 --- /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 GenericEvent + */ + 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 GenericEvent + */ + 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..12a74531 --- /dev/null +++ b/vendor/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/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..30429d3f --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/AbstractEventDispatcherTest.php @@ -0,0 +1,373 @@ + + * + * 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\Event; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +abstract class AbstractEventDispatcherTest extends \PHPUnit_Framework_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(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()); + } +} + +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..04b1ec14 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php @@ -0,0 +1,207 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest +{ + protected function createEventDispatcher() + { + $container = new Container(); + + return new ContainerAwareEventDispatcher($container); + } + + public function testAddAListenerService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $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->getMock('Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $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->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $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->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $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->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $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->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $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->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $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..2dd8292a --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,216 @@ + + * + * 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 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 \PHPUnit_Framework_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 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() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', $listener = function () {}); + + $this->assertEquals(array(), $tdispatcher->getCalledListeners()); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => 0)), $tdispatcher->getNotCalledListeners()); + + $tdispatcher->dispatch('foo'); + + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure', 'priority' => null)), $tdispatcher->getCalledListeners()); + $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->getMock('Psr\Log\LoggerInterface'); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function () {}); + $tdispatcher->addListener('foo', $listener2 = function () {}); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".'); + $logger->expects($this->at(1))->method('debug')->with('Notified event "foo" to listener "closure".'); + + $tdispatcher->dispatch('foo'); + } + + public function testLoggerWithStoppedEvent() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); }); + $tdispatcher->addListener('foo', $listener2 = function () {}); + + $logger->expects($this->at(0))->method('debug')->with('Notified event "foo" to listener "closure".'); + $logger->expects($this->at(1))->method('debug')->with('Listener "closure" stopped propagation of the event "foo".'); + $logger->expects($this->at(2))->method('debug')->with('Listener "closure" was not called for event "foo".'); + + $tdispatcher->dispatch('foo'); + } + + public function testDispatchCallListeners() + { + $called = array(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo1'; }, 10); + $tdispatcher->addListener('foo', function () use (&$called) { $called[] = 'foo2'; }, 20); + + $tdispatcher->dispatch('foo'); + + $this->assertSame(array('foo2', 'foo1'), $called); + } + + public function testDispatchNested() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $loop = 1; + $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) { + ++$loop; + if (2 == $loop) { + $dispatcher->dispatch('foo'); + } + }); + + $dispatcher->dispatch('foo'); + } + + 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..0fdd6372 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -0,0 +1,200 @@ + + * + * 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 Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; + +class RegisterListenersPassTest extends \PHPUnit_Framework_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->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('isPublic') + ->will($this->returnValue(true)); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('stdClass')); + + $builder = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('hasDefinition', 'findTaggedServiceIds', 'getDefinition') + ); + $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->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('isPublic') + ->will($this->returnValue(true)); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService')); + + $builder = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('hasDefinition', 'findTaggedServiceIds', 'getDefinition', 'findDefinition') + ); + $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" must be public as event listeners are lazy-loaded. + */ + public function testPrivateEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must be public as event subscribers are lazy-loaded. + */ + public function testPrivateEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setPublic(false)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must not be abstract as event listeners are lazy-loaded. + */ + public function testAbstractEventListener() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_listener', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The service "foo" must not be abstract as event subscribers are lazy-loaded. + */ + public function testAbstractEventSubscriber() + { + $container = new ContainerBuilder(); + $container->register('foo', 'stdClass')->setAbstract(true)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } + + public function testEventSubscriberResolvableClassName() + { + $container = new ContainerBuilder(); + + $container->setParameter('subscriber.class', 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService'); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + + $definition = $container->getDefinition('event_dispatcher'); + $expected_calls = array( + array( + 'addSubscriberService', + array( + 'foo', + 'Symfony\Component\EventDispatcher\Tests\DependencyInjection\SubscriberService', + ), + ), + ); + $this->assertSame($expected_calls, $definition->getMethodCalls()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class" + */ + public function testEventSubscriberUnresolvableClassName() + { + $container = new ContainerBuilder(); + $container->register('foo', '%subscriber.class%')->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($container); + } +} + +class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + } +} diff --git a/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php b/vendor/symfony/event-dispatcher/Tests/EventDispatcherTest.php new file mode 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..1a6f4c4a --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/EventTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; + +/** + * Test class for Event. + */ +class EventTest extends \PHPUnit_Framework_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..aebd82da --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/GenericEventTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\GenericEvent; + +/** + * Test class for Event. + */ +class GenericEventTest extends \PHPUnit_Framework_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->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..80a7e43b --- /dev/null +++ b/vendor/symfony/event-dispatcher/Tests/ImmutableEventDispatcherTest.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\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\ImmutableEventDispatcher; + +/** + * @author Bernhard Schussek + */ +class ImmutableEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $innerDispatcher; + + /** + * @var ImmutableEventDispatcher + */ + private $dispatcher; + + protected function setUp() + { + $this->innerDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $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->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveListenerDisallowed() + { + $this->dispatcher->removeListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveSubscriberDisallowed() + { + $subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $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..12d91b92 --- /dev/null +++ b/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Symfony EventDispatcher Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/config": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.0-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..ae0586e0 --- /dev/null +++ b/vendor/symfony/event-dispatcher/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + + + + + ./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..22607876 --- /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 AcceptHeader + */ + 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 AcceptHeader + */ + 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 AcceptHeader + */ + 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..21a5d155 --- /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 AcceptHeaderItem + */ + 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 AcceptHeaderItem + */ + 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 AcceptHeaderItem + */ + 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 AcceptHeaderItem + */ + 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 AcceptHeaderItem + */ + 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..a39984df --- /dev/null +++ b/vendor/symfony/http-foundation/BinaryFileResponse.php @@ -0,0 +1,359 @@ + + * + * 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 BinaryFileResponse The created response + */ + 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 BinaryFileResponse + * + * @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 BinaryFileResponse + */ + 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; $i < mb_strlen($filename, $encoding); ++$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) + { + $this->headers->set('Content-Length', $this->file->getSize()); + + if (!$this->headers->has('Accept-Ranges')) { + // Only accept ranges on safe HTTP methods + $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none'); + } + + 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 (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'); + $fileSize = $this->file->getSize(); + + 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 BinaryFileResponse + */ + 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..ca98c04c --- /dev/null +++ b/vendor/symfony/http-foundation/CHANGELOG.md @@ -0,0 +1,133 @@ +CHANGELOG +========= + +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..01ff8990 --- /dev/null +++ b/vendor/symfony/http-foundation/Cookie.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 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; + + /** + * Constructor. + * + * @param string $name The name of the cookie + * @param string $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 $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 + * + * @throws \InvalidArgumentException + */ + public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true) + { + // 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 || -1 === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = $expire; + $this->path = empty($path) ? '/' : $path; + $this->secure = (bool) $secure; + $this->httpOnly = (bool) $httpOnly; + } + + /** + * Returns the cookie as a string. + * + * @return string The cookie + */ + public function __toString() + { + $str = urlencode($this->getName()).'='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001); + } else { + $str .= urlencode($this->getValue()); + + if ($this->getExpiresTime() !== 0) { + $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()); + } + } + + if ($this->path) { + $str .= '; path='.$this->path; + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if (true === $this->isSecure()) { + $str .= '; secure'; + } + + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + + return $str; + } + + /** + * Gets the name of the cookie. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + * + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + * + * @return int + */ + public function getExpiresTime() + { + return $this->expire; + } + + /** + * 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(); + } +} diff --git a/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php new file mode 100644 index 00000000..fa5f1c78 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.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\HttpFoundation\Exception; + +/** + * The HTTP request contains headers with conflicting information. + * + * This exception should trigger an HTTP 400 response in your application code. + * + * @author Magnus Nordlander + */ +class ConflictingHeadersException extends \RuntimeException +{ +} 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..4736b45c --- /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 File 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..ec9b78ab --- /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 ExtensionGuesser + */ + 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..a7e4ae2e --- /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 + * + * @link 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..17fd344b --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php @@ -0,0 +1,808 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\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/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..ecc8a30a --- /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 MimeTypeGuesser + */ + 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/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..197eab42 --- /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 array 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..29bac5e5 --- /dev/null +++ b/vendor/symfony/http-foundation/HeaderBag.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\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 (!$this->headers) { + return ''; + } + + $max = max(array_map('strlen', array_keys($this->headers))) + 1; + $content = ''; + ksort($this->headers); + foreach ($this->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->headers); + } + + /** + * 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)); + + if (!array_key_exists($key, $this->headers)) { + if (null === $default) { + return $first ? null : array(); + } + + return $first ? $default : array($default); + } + + if ($first) { + return count($this->headers[$key]) ? $this->headers[$key][0] : $default; + } + + return $this->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->headers); + } + + /** + * 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..82461d01 --- /dev/null +++ b/vendor/symfony/http-foundation/IpUtils.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; + +/** + * 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 (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ($netmask === '0') { + // Ensure IP is valid - using ip2long below implicitly validates, but we need to do it manually here + 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..075e500b --- /dev/null +++ b/vendor/symfony/http-foundation/JsonResponse.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; + +/** + * 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 " for RFC4627-compliant JSON, which may also be embedded into HTML. + // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + protected $encodingOptions = 15; + + /** + * Constructor. + * + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + */ + public function __construct($data = null, $status = 200, $headers = array()) + { + parent::__construct('', $status, $headers); + + if (null === $data) { + $data = new \ArrayObject(); + } + + $this->setData($data); + } + + /** + * {@inheritdoc} + */ + public static function create($data = null, $status = 200, $headers = array()) + { + return new static($data, $status, $headers); + } + + /** + * Sets the JSONP callback. + * + * @param string|null $callback The JSONP callback or null to use none + * + * @return JsonResponse + * + * @throws \InvalidArgumentException When the callback name is not valid + */ + public function setCallback($callback = null) + { + if (null !== $callback) { + // taken from http://www.geekality.net/2011/08/03/valid-javascript-identifier/ + $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*+$/u'; + $parts = explode('.', $callback); + foreach ($parts as $part) { + if (!preg_match($pattern, $part)) { + throw new \InvalidArgumentException('The callback name is not valid.'); + } + } + } + + $this->callback = $callback; + + return $this->update(); + } + + /** + * Sets the data to be sent as JSON. + * + * @param mixed $data + * + * @return JsonResponse + * + * @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()); + } + + $this->data = $data; + + return $this->update(); + } + + /** + * 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 JsonResponse + */ + 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 JsonResponse + */ + 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..12a74531 --- /dev/null +++ b/vendor/symfony/http-foundation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/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..8d260833 --- /dev/null +++ b/vendor/symfony/http-foundation/RedirectResponse.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\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)); + } + } + + /** + * {@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 RedirectResponse The current response + * + * @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..d846a3a8 --- /dev/null +++ b/vendor/symfony/http-foundation/Request.php @@ -0,0 +1,1951 @@ + + * + * 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\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 = 'forwarded'; + const HEADER_CLIENT_IP = 'client_ip'; + const HEADER_CLIENT_HOST = 'client_host'; + const HEADER_CLIENT_PROTO = 'client_proto'; + const HEADER_CLIENT_PORT = 'client_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). + */ + 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; + + /** + * 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 Request A new request + */ + 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 Request A Request instance + */ + 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 Request The duplicated request + */ + 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 + */ + public static function setTrustedProxies(array $proxies) + { + self::$trustedProxies = $proxies; + } + + /** + * Gets the list of trusted proxies. + * + * @return array An array of trusted proxies + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + + /** + * 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()) + * + * 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 + */ + public static function setTrustedHeaderName($key, $value) + { + 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; + } + + /** + * Gets the trusted proxy header name. + * + * @param string $key The header key + * + * @return string The header name + * + * @throws \InvalidArgumentException + */ + public static function getTrustedHeaderName($key) + { + 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() + { + $clientIps = array(); + $ip = $this->server->get('REMOTE_ADDR'); + + if (!$this->isFromTrustedProxy()) { + return array($ip); + } + + $hasTrustedForwardedHeader = self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED]); + $hasTrustedClientIpHeader = self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP]); + + if ($hasTrustedForwardedHeader) { + $forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]); + preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches); + $forwardedClientIps = $matches[3]; + + $forwardedClientIps = $this->normalizeAndFilterClientIps($forwardedClientIps, $ip); + $clientIps = $forwardedClientIps; + } + + if ($hasTrustedClientIpHeader) { + $xForwardedForClientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP]))); + + $xForwardedForClientIps = $this->normalizeAndFilterClientIps($xForwardedForClientIps, $ip); + $clientIps = $xForwardedForClientIps; + } + + if ($hasTrustedForwardedHeader && $hasTrustedClientIpHeader && $forwardedClientIps !== $xForwardedForClientIps) { + throw new ConflictingHeadersException('The request has both a trusted Forwarded header and a trusted Client IP header, conflicting with each other with regards to the originating IP addresses of the request. This is the result of a misconfiguration. You should either configure your proxy only to send one of these headers, or configure Symfony to distrust one of them.'); + } + + if (!$hasTrustedForwardedHeader && !$hasTrustedClientIpHeader) { + return $this->normalizeAndFilterClientIps(array(), $ip); + } + + return $clientIps; + } + + /** + * 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 "setTrustedHeaderName()" with + * the "client-ip" key. + * + * @return string 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 "setTrustedHeaderName()" with the "client-port" key. + * + * @return string + */ + public function getPort() + { + if ($this->isFromTrustedProxy()) { + if (self::$trustedHeaders[self::HEADER_CLIENT_PORT] && $port = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PORT])) { + return $port; + } + + if (self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && 'https' === $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO], 'http')) { + return 443; + } + } + + if ($host = $this->headers->get('HOST')) { + 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; + } + + return $this->server->get('SERVER_PORT'); + } + + /** + * 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 "setTrustedHeaderName()" with + * the "client-proto" key. + * + * @return bool + */ + public function isSecure() + { + if ($this->isFromTrustedProxy() && self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && $proto = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO])) { + return in_array(strtolower(current(explode(',', $proto))), array('https', 'on', 'ssl', '1')); + } + + $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 "setTrustedHeaderName()" with the "client-host" key. + * + * @return string + * + * @throws \UnexpectedValueException when the host name is invalid + */ + public function getHost() + { + if ($this->isFromTrustedProxy() && self::$trustedHeaders[self::HEADER_CLIENT_HOST] && $host = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_HOST])) { + $elements = explode(',', $host); + + $host = $elements[count($elements) - 1]; + } 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)) { + throw new \UnexpectedValueException(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; + } + } + + throw new \UnexpectedValueException(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 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', $default); + } + + return $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 the method is safe or not. + * + * @return bool + */ + public function isMethodSafe() + { + return in_array($this->getMethod(), array('GET', 'HEAD', 'OPTIONS', 'TRACE')); + } + + /** + * 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) { + $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: + * + * @link 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); + } + + private function isFromTrustedProxy() + { + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies); + } + + private function normalizeAndFilterClientIps(array $clientIps, $ip) + { + $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..ca094ca1 --- /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 + */ + private $path; + + /** + * @var string + */ + private $host; + + /** + * @var array + */ + 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 = array_map('strtolower', (array) $scheme); + } + + /** + * Adds a check for the URL host name. + * + * @param string $regexp A Regexp + */ + public function matchHost($regexp) + { + $this->host = $regexp; + } + + /** + * Adds a check for the URL path info. + * + * @param string $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[] $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIps($ips) + { + $this->ips = (array) $ips; + } + + /** + * Adds a check for the HTTP method. + * + * @param string|string[] $method An HTTP method or an array of HTTP methods + */ + public function matchMethod($method) + { + $this->methods = array_map('strtoupper', (array) $method); + } + + /** + * Adds a check for request attribute. + * + * @param string $key The request attribute name + * @param string $regexp A Regexp + */ + public function matchAttribute($key, $regexp) + { + $this->attributes[$key] = $regexp; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + if ($this->schemes && !in_array($request->getScheme(), $this->schemes)) { + return false; + } + + if ($this->methods && !in_array($request->getMethod(), $this->methods)) { + 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..6b723f9a --- /dev/null +++ b/vendor/symfony/http-foundation/Response.php @@ -0,0 +1,1181 @@ + + * + * 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 (Experimental)', // 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'); + } + + /** + * 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 Response + */ + 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 Response The current response + */ + 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() && 'no-cache' == $this->headers->get('Cache-Control')) { + $this->headers->set('pragma', 'no-cache'); + $this->headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return Response + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + + // headers + foreach ($this->headers->allPreserveCase() 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) { + 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 Response + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return Response + */ + 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 Response + * + * @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 Response + */ + public function setProtocolVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @return string The HTTP protocol version + */ + 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 Response + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + 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 + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @param string $charset Character set + * + * @return Response + */ + public function setCharset($charset) + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @return string Character set + */ + 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 + */ + 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 + */ + 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 + */ + 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 Response + */ + 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 Response + */ + 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 + */ + 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 + */ + public function getDate() + { + 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 Response + */ + 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 + */ + 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 Response + */ + 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 + */ + 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 Response + */ + 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 + */ + 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 Response + */ + 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 Response + */ + 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 + */ + 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 Response + */ + 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 Response + */ + 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 + */ + 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 Response + */ + 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 + */ + 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 Response + */ + 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 Response + * + * @throws \InvalidArgumentException + */ + 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 Response + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3.5 + */ + 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 + */ + 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 + */ + 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 Response + */ + 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 + */ + public function isNotModified(Request $request) + { + if (!$request->isMethodSafe()) { + 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 + */ + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @return bool + */ + public function isInformational() + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @return bool + */ + public function isSuccessful() + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @return bool + */ + public function isRedirection() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @return bool + */ + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @return bool + */ + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @return bool + */ + public function isOk() + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @return bool + */ + public function isForbidden() + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @return bool + */ + public function isNotFound() + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @param string $location + * + * @return bool + */ + 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 + */ + 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 + */ + 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. + * + * @link http://support.microsoft.com/kb/323308 + */ + 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..3223691e --- /dev/null +++ b/vendor/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\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', ''); + } + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $cookies = ''; + foreach ($this->getCookies() as $cookie) { + $cookies .= 'Set-Cookie: '.$cookie."\r\n"; + } + + ksort($this->headerNames); + + return parent::__toString().$cookies; + } + + /** + * Returns the headers, with original capitalizations. + * + * @return array An array of headers + */ + public function allPreserveCase() + { + return array_combine($this->headerNames, $this->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 set($key, $values, $replace = true) + { + parent::set($key, $values, $replace); + + $uniqueKey = str_replace('_', '-', strtolower($key)); + $this->headerNames[$uniqueKey] = $key; + + // 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) + { + parent::remove($key); + + $uniqueKey = str_replace('_', '-', strtolower($key)); + unset($this->headerNames[$uniqueKey]); + + 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; + } + + /** + * 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]); + } + } + } + + /** + * 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'; + } + + 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..80e97f17 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.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\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. + */ + 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..cdd97375 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Session.php @@ -0,0 +1,249 @@ + + * + * 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->storage->getBag($this->attributeName)->has($name); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return $this->storage->getBag($this->attributeName)->get($name, $default); + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->storage->getBag($this->attributeName)->set($name, $value); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->storage->getBag($this->attributeName)->all(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->storage->getBag($this->attributeName)->replace($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + return $this->storage->getBag($this->attributeName)->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->storage->getBag($this->attributeName)->all()); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return count($this->storage->getBag($this->attributeName)->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); + } +} 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..a386bdd1 --- /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 $this->memcache->close(); + } + + /** + * {@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..7efc1348 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,230 @@ + + * + * 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. + */ + 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..c26cc133 --- /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 + */ + protected $bags; + + /** + * 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..0b096174 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,398 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\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_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() + { + session_write_close(); + + $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_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..c5e97d41 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.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\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'; + } + + // \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..b021d3c4 --- /dev/null +++ b/vendor/symfony/http-foundation/StreamedResponse.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\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; + + /** + * 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; + } + + /** + * 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 StreamedResponse + */ + 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 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..e4f354fc --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderItemTest.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\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\AcceptHeaderItem; + +class AcceptHeaderItemTest extends \PHPUnit_Framework_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..9b3b58e2 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/AcceptHeaderTest.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\Tests; + +use Symfony\Component\HttpFoundation\AcceptHeader; +use Symfony\Component\HttpFoundation\AcceptHeaderItem; + +class AcceptHeaderTest extends \PHPUnit_Framework_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..6845118c --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ApacheRequestTest.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\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\ApacheRequest; + +class ApacheRequestTest extends \PHPUnit_Framework_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..e4607201 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/BinaryFileResponseTest.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\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\BinaryFileResponse; +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')); + } + + /** + * @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'), + ); + } + + 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..22278653 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/CookieTest.php @@ -0,0 +1,154 @@ + + * + * 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\Cookie; + +/** + * CookieTest. + * + * @author John Kary + * @author Hugo Hamon + * + * @group time-sensitive + */ +class CookieTest extends \PHPUnit_Framework_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() + { + $cookie = new Cookie('MyCookie', 'foo', 'bar'); + } + + 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', 3600); + + $this->assertEquals(3600, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + 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', 3600, '/', '.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', 3600, '/', '.myfoodomain.com', true); + + $this->assertTrue($cookie->isSecure(), '->isSecure() returns whether the cookie is transmitted over HTTPS'); + } + + public function testIsHttpOnly() + { + $cookie = new Cookie('foo', 'bar', 3600, '/', '.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', 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; path=/; domain=.myfoodomain.com; secure; httponly', $cookie->__toString(), '->__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', time() - 31536001).'; path=/admin/; domain=.myfoodomain.com; httponly', $cookie->__toString(), '->__toString() returns string representation of a cleared cookie if value is NULL'); + + $cookie = new Cookie('foo', 'bar', 0, '/', ''); + $this->assertEquals('foo=bar; path=/; httponly', $cookie->__toString()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.php new file mode 100644 index 00000000..fda372f3 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ExpressionRequestMatcherTest.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\HttpFoundation\Tests; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\ExpressionRequestMatcher; +use Symfony\Component\HttpFoundation\Request; + +class ExpressionRequestMatcherTest extends \PHPUnit_Framework_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..a92f5728 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/FileTest.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; + +class FileTest extends \PHPUnit_Framework_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->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->getMock('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface'); + $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..1d5648ea --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/MimeType/MimeTypeTest.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\File\MimeType; + +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser; + +/** + * @requires extension fileinfo + */ +class MimeTypeTest extends \PHPUnit_Framework_TestCase +{ + protected $path; + + public function testGuessImageWithoutExtension() + { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithDirectory() + { + $this->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->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->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..08372507 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/File/UploadedFileTest.php @@ -0,0 +1,272 @@ + + * + * 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\UploadedFile; + +class UploadedFileTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!ini_get('file_uploads')) { + $this->markTestSkipped('file_uploads is disabled in php.ini'); + } + } + + public function testConstructWhenFileNotExists() + { + $this->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..738604bc --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/FileBagTest.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\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\FileBag; + +/** + * FileBagTest. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBagTest extends \PHPUnit_Framework_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..d4d02d94 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/HeaderBagTest.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\HeaderBag; + +class HeaderBagTest extends \PHPUnit_Framework_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 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..877053f0 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/IpUtilsTest.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\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\IpUtils; + +class IpUtilsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider testIpv4Provider + */ + public function testIpv4($matches, $remoteAddr, $cidr) + { + $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); + } + + public function testIpv4Provider() + { + 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 + ); + } + + /** + * @dataProvider testIpv6Provider + */ + 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 testIpv6Provider() + { + 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..44f26470 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/JsonResponseTest.php @@ -0,0 +1,225 @@ + + * + * 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\JsonResponse; + +class JsonResponseTest extends \PHPUnit_Framework_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 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()); + } + + /** + * @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); + } +} + +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..1f724d41 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ParameterBagTest.php @@ -0,0 +1,193 @@ + + * + * 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\ParameterBag; + +class ParameterBagTest extends \PHPUnit_Framework_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..2a097d6f --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RedirectResponseTest.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\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\RedirectResponse; + +class RedirectResponseTest extends \PHPUnit_Framework_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()); + } +} diff --git a/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php new file mode 100644 index 00000000..cee9bcc2 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RequestMatcherTest.php @@ -0,0 +1,150 @@ + + * + * 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\RequestMatcher; +use Symfony\Component\HttpFoundation\Request; + +class RequestMatcherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider testMethodFixtures + */ + 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 testMethodFixtures() + { + 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 testHostFixture + */ + 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 testHostFixture() + { + 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..e26b806f --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RequestStackTest.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 Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; + +class RequestStackTest extends \PHPUnit_Framework_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..88ab118a --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/RequestTest.php @@ -0,0 +1,1979 @@ + + * + * 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\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Request; + +class RequestTest extends \PHPUnit_Framework_TestCase +{ + 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 getFormatToMimeTypeMapProvider + */ + 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)); + } + } + + public function testGetFormatFromMimeTypeWithParameters() + { + $request = new Request(); + $this->assertEquals('json', $request->getFormat('application/json; charset=utf-8')); + } + + /** + * @dataProvider getFormatToMimeTypeMapProvider + */ + public function testGetMimeTypeFromFormat($format, $mimeTypes) + { + if (null !== $format) { + $request = new Request(); + $this->assertEquals($mimeTypes[0], $request->getMimeType($format)); + } + } + + 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(null, array(null, 'unexistent-mime-type')), + 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 = 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.'); + + Request::setTrustedProxies(array()); + } + + /** + * @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 testGetClientIpsProvider + */ + public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); + + $this->assertEquals($expected[0], $request->getClientIp()); + + Request::setTrustedProxies(array()); + } + + /** + * @dataProvider testGetClientIpsProvider + */ + public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); + + $this->assertEquals($expected, $request->getClientIps()); + + Request::setTrustedProxies(array()); + } + + /** + * @dataProvider testGetClientIpsForwardedProvider + */ + public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies); + + $this->assertEquals($expected, $request->getClientIps()); + + Request::setTrustedProxies(array()); + } + + public function testGetClientIpsForwardedProvider() + { + // $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 testGetClientIpsProvider() + { + // $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 testGetClientIpsWithConflictingHeadersProvider + */ + 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->initialize(array(), array(), array(), array(), array(), $server); + + $request->getClientIps(); + } + + public function testGetClientIpsWithConflictingHeadersProvider() + { + // $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 testGetClientIpsWithAgreeingHeadersProvider + */ + public function testGetClientIpsWithAgreeingHeaders($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->initialize(array(), array(), array(), array(), array(), $server); + + $request->getClientIps(); + + Request::setTrustedProxies(array()); + } + + public function testGetClientIpsWithAgreeingHeadersProvider() + { + // $httpForwarded $httpXForwardedFor + return array( + array('for="192.0.2.60"', '192.0.2.60'), + array('for=192.0.2.60, for=87.65.43.21', '192.0.2.60,87.65.43.21'), + array('for="[::face]", for=192.0.2.60', '::face,192.0.2.60'), + array('for="192.0.2.60:80"', '192.0.2.60'), + array('for=192.0.2.60;proto=http;by=203.0.113.43', '192.0.2.60'), + array('for="[2001:db8:cafe::17]:4711"', '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->assertTrue(is_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); + } + + /** + * @dataProvider getContentCantBeCalledTwiceWithResourcesProvider + * @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->assertEquals($a, $b); + } + + public function getContentCantBeCalledTwiceWithResourcesProvider() + { + return array( + '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')); + $this->assertFalse($request->isSecure()); + $request->server->set('REMOTE_ADDR', '1.1.1.1'); + $this->assertTrue($request->isSecure()); + Request::setTrustedProxies(array()); + + $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()); + + $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->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->initialize(array(), array(), array(), array(), array(), $server); + + return $request; + } + + public function testTrustedProxies() + { + $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); + + // 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()); + $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')); + $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')); + $this->assertEquals('1.1.1.1', $request->getClientIp()); + $this->assertEquals('real.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')); + $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->headers->set('X_FORWARDED_PROTO', 'ssl'); + $this->assertTrue($request->isSecure()); + + $request->headers->set('X_FORWARDED_PROTO', 'https, http'); + $this->assertTrue($request->isSecure()); + + // 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::setTrustedProxies(array()); + 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'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetTrustedProxiesInvalidHeaderName() + { + Request::create('http://example.com/'); + Request::setTrustedHeaderName('bogus name', 'X_MY_FOR'); + } + + /** + * @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 (\UnexpectedValueException $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 { + $this->setExpectedException('UnexpectedValueException', '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 methodSafeProvider + */ + public function testMethodSafe($method, $safe) + { + $request = new Request(); + $request->setMethod($method); + $this->assertEquals($safe, $request->isMethodSafe()); + } + + 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), + ); + } +} + +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..8e487d61 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ResponseHeaderBagTest.php @@ -0,0 +1,297 @@ + + * + * 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\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\Cookie; + +/** + * @group time-sensitive + */ +class ResponseHeaderBagTest extends \PHPUnit_Framework_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')), + ), + 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')), + ), + array( + array('P3P' => 'CP="CAO PSA OUR"'), + array('P3P' => array('CP="CAO PSA OUR"'), 'Cache-Control' => array('no-cache')), + ), + array( + array('WWW-Authenticate' => 'Basic realm="WallyWorld"'), + array('WWW-Authenticate' => array('Basic realm="WallyWorld"'), 'Cache-Control' => array('no-cache')), + ), + array( + array('X-UA-Compatible' => 'IE=edge,chrome=1'), + array('X-UA-Compatible' => array('IE=edge,chrome=1'), 'Cache-Control' => array('no-cache')), + ), + array( + array('X-XSS-Protection' => '1; mode=block'), + array('X-XSS-Protection' => array('1; mode=block'), 'Cache-Control' => array('no-cache')), + ), + ); + } + + public function testCacheControlHeader() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache', $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 testToStringIncludesCookieHeaders() + { + $bag = new ResponseHeaderBag(array()); + $bag->setCookie(new Cookie('foo', 'bar')); + + $this->assertContains('Set-Cookie: foo=bar; path=/; httponly', explode("\r\n", $bag->__toString())); + + $bag->clearCookie('foo'); + + $this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; httponly#m', $bag->__toString()); + } + + public function testClearCookieSecureNotHttpOnly() + { + $bag = new ResponseHeaderBag(array()); + + $bag->clearCookie('foo', '/', null, true, false); + + $this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; secure#m', $bag->__toString()); + } + + public function testReplace() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache', $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', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag->remove('Cache-Control'); + $bag->replace(array()); + $this->assertEquals('no-cache', $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()); + + $headers = explode("\r\n", $bag->__toString()); + $this->assertContains('Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly', $headers); + $this->assertContains('Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly', $headers); + $this->assertContains('Set-Cookie: foo=bar; path=/path/bar; domain=bar.foo; httponly', $headers); + $this->assertContains('Set-Cookie: foo=bar; path=/; httponly', $headers); + + $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(); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar')); + $bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar')); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertTrue(isset($cookies['foo.bar']['/path/foo'])); + + $bag->removeCookie('foo', '/path/foo', 'foo.bar'); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['foo.bar']['/path/foo'])); + + $bag->removeCookie('bar', '/path/bar', 'foo.bar'); + + $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'])); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetCookiesWithInvalidArgument() + { + $bag = new ResponseHeaderBag(); + + $cookies = $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'), + ); + } +} diff --git a/vendor/symfony/http-foundation/Tests/ResponseTest.php b/vendor/symfony/http-foundation/Tests/ResponseTest.php new file mode 100644 index 00000000..97674d48 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ResponseTest.php @@ -0,0 +1,893 @@ + + * + * 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', $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); + $response->headers->remove('Date'); + $this->assertInstanceOf('\DateTime', $response->getDate()); + } + + 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->getMock('Symfony\Component\HttpFoundation\ResponseHeaderBag', array('set')); + $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 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(); + } +} + +class StringableObject +{ + public function __toString() + { + return 'Foo'; + } +} diff --git a/vendor/symfony/http-foundation/Tests/ResponseTestCase.php b/vendor/symfony/http-foundation/Tests/ResponseTestCase.php new file mode 100644 index 00000000..94c770a8 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ResponseTestCase.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\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\Request; + +abstract class ResponseTestCase extends \PHPUnit_Framework_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..41e44e10 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/ServerBagTest.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\ServerBag; + +/** + * ServerBagTest. + * + * @author Bulat Shakirzyanov + */ +class ServerBagTest extends \PHPUnit_Framework_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..ca6ce8a3 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/AttributeBagTest.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\HttpFoundation\Tests\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Tests AttributeBag. + * + * @author Drak + */ +class AttributeBagTest extends \PHPUnit_Framework_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..470038fe --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php @@ -0,0 +1,184 @@ + + * + * 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 Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag; + +/** + * Tests NamespacedAttributeBag. + * + * @author Drak + */ +class NamespacedAttributeBagTest extends \PHPUnit_Framework_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..852158f1 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Flash/AutoExpireFlashBagTest.php @@ -0,0 +1,155 @@ + + * + * 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 Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag as FlashBag; + +/** + * AutoExpireFlashBagTest. + * + * @author Drak + */ +class AutoExpireFlashBagTest extends \PHPUnit_Framework_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..fc3658de --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Flash/FlashBagTest.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 Symfony\Component\HttpFoundation\Tests\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; + +/** + * FlashBagTest. + * + * @author Drak + */ +class FlashBagTest extends \PHPUnit_Framework_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..57460c6d --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/SessionTest.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session; + +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 \PHPUnit_Framework_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(); + } + + 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..0c579d77 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.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\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcacheSessionHandler; + +/** + * @requires extension memcache + * @group time-sensitive + */ +class MemcacheSessionHandlerTest extends \PHPUnit_Framework_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->getMock('Memcache'); + $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->memcache + ->expects($this->once()) + ->method('close') + ->will($this->returnValue(true)) + ; + + $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..75e4a20e --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.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\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler; + +/** + * @requires extension memcached + * @group time-sensitive + */ +class MemcachedSessionHandlerTest extends \PHPUnit_Framework_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', '>=')) { + $this->markTestSkipped('Tests can only be run with memcached extension 2.1.0 or lower'); + } + + $this->memcached = $this->getMock('Memcached'); + $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..a76108d9 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -0,0 +1,327 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file this was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler; + +/** + * @author Markus Bachmann + * @group time-sensitive + */ +class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $mongo; + private $storage; + public $options; + + protected function setUp() + { + parent::setUp(); + + if (!extension_loaded('mongo') && !extension_loaded('mongodb')) { + $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(intval((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(intval((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(intval((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..c3eeacf7 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.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\HttpFoundation\Tests\Session\Storage\Handler; + +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 \PHPUnit_Framework_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..b2304cbc --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.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\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; + +/** + * Test class for NativeSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class NativeSessionHandlerTest extends \PHPUnit_Framework_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..35823d68 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.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\Tests\Session\Storage\Handler; + +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 \PHPUnit_Framework_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..1f88d00d --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -0,0 +1,369 @@ + + * + * 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 Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + +/** + * @requires extension pdo_sqlite + * @group time-sensitive + */ +class PdoSessionHandlerTest extends \PHPUnit_Framework_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->getMock('PDOStatement'); + + $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->getMock('PDOStatement'); + $insertStmt = $this->getMock('PDOStatement'); + + $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..069c8870 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.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\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler; + +/** + * @author Adrien Brault + */ +class WriteCheckSessionHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function test() + { + $wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface'); + $writeCheckSessionHandler = new WriteCheckSessionHandler($wrappedSessionHandlerMock); + + $wrappedSessionHandlerMock + ->expects($this->once()) + ->method('close') + ->with() + ->will($this->returnValue(true)) + ; + + $this->assertTrue($writeCheckSessionHandler->close()); + } + + public function testWrite() + { + $wrappedSessionHandlerMock = $this->getMock('SessionHandlerInterface'); + $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->getMock('SessionHandlerInterface'); + $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->getMock('SessionHandlerInterface'); + $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..2fa473fb --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MetadataBagTest.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\Tests\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Test class for MetadataBag. + * + * @group time-sensitive + */ +class MetadataBagTest extends \PHPUnit_Framework_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(); + } + + 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..c56ea4a6 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockArraySessionStorageTest.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\HttpFoundation\Tests\Session\Storage; + +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 \PHPUnit_Framework_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()); + } + + /** + * @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..54321ea4 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/MockFileSessionStorageTest.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\HttpFoundation\Tests\Session\Storage; + +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 \PHPUnit_Framework_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..ee3dfee7 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -0,0 +1,246 @@ + + * + * 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 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 \PHPUnit_Framework_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..b349277a --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.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; + +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 \PHPUnit_Framework_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..b29c4332 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/AbstractProxyTest.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\Tests\Session\Storage\Proxy; + +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 \PHPUnit_Framework_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..e9184ce0 --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/NativeProxyTest.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\Tests\Session\Storage\Proxy; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy; + +/** + * Test class for NativeProxy. + * + * @author Drak + */ +class NativeProxyTest extends \PHPUnit_Framework_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..bc810a6b --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.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\HttpFoundation\Tests\Session\Storage\Proxy; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * Tests for SessionHandlerProxy class. + * + * @author Drak + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class SessionHandlerProxyTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_Matcher + */ + private $mock; + + /** + * @var SessionHandlerProxy + */ + private $proxy; + + protected function setUp() + { + $this->mock = $this->getMock('SessionHandlerInterface'); + $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..121459cc --- /dev/null +++ b/vendor/symfony/http-foundation/Tests/StreamedResponseTest.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\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\StreamedResponse; + +class StreamedResponseTest extends \PHPUnit_Framework_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'; }); + $request = Request::create('/', 'HEAD'); + + $response->prepare($request); + } + + 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/composer.json b/vendor/symfony/http-foundation/composer.json new file mode 100644 index 00000000..63495512 --- /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.0-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..9ffdb43a --- /dev/null +++ b/vendor/symfony/http-foundation/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + + + + + ./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..8e9cadad --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/Bundle.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace 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; + + /** + * 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() + { + $class = get_class($this); + + return substr($class, 0, strrpos($class, '\\')); + } + + /** + * 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 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) { + return $this->name; + } + + $name = get_class($this); + $pos = strrpos($name, '\\'); + + return $this->name = false === $pos ? $name : substr($name, $pos + 1); + } + + /** + * 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) { + $alias = 'console.command.'.strtolower(str_replace('\\', '_', $class)); + if ($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(); + } + } +} 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..5ca85c67 --- /dev/null +++ b/vendor/symfony/http-kernel/CHANGELOG.md @@ -0,0 +1,104 @@ +CHANGELOG +========= + +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/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..80b1bd6c --- /dev/null +++ b/vendor/symfony/http-kernel/Client.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\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\Cookie as DomCookie; +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 + */ +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); + } + + /** + * {@inheritdoc} + * + * @return Request|null A Request instance + */ + public function getRequest() + { + return parent::getRequest(); + } + + /** + * {@inheritdoc} + * + * @return Response|null A Response instance + */ + public function getResponse() + { + return parent::getResponse(); + } + + /** + * 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)); + + $r = new \ReflectionClass('\\Symfony\\Component\\ClassLoader\\ClassLoader'); + $requirePath = str_replace("'", "\\'", $r->getFileName()); + $symfonyPath = str_replace("'", "\\'", dirname(dirname(dirname(__DIR__)))); + $errorReporting = error_reporting(); + + $code = <<addPrefix('Symfony', '$symfonyPath'); +\$loader->register(); + +\$kernel = unserialize('$kernel'); +\$request = unserialize('$request'); +EOF; + + return $code.$this->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) + { + $headers = $response->headers->all(); + if ($response->headers->getCookies()) { + $cookies = array(); + foreach ($response->headers->getCookies() as $cookie) { + $cookies[] = new DomCookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + $headers['Set-Cookie'] = $cookies; + } + + // this is needed to support StreamedResponse + ob_start(); + $response->sendContent(); + $content = ob_get_clean(); + + return new DomResponse($content, $response->getStatusCode(), $headers); + } +} diff --git a/vendor/symfony/http-kernel/Config/EnvParametersResource.php b/vendor/symfony/http-kernel/Config/EnvParametersResource.php new file mode 100644 index 00000000..bad199be --- /dev/null +++ b/vendor/symfony/http-kernel/Config/EnvParametersResource.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\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) + { + $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/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..56d1b97a --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolver.php @@ -0,0 +1,229 @@ + + * + * 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 ControllerResolverInterface +{ + private $logger; + + /** + * Constructor. + * + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + } + + /** + * {@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} + */ + public function getArguments(Request $request, $controller) + { + 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()); + } + + protected function doGetArguments(Request $request, $controller, array $parameters) + { + $attributes = $request->attributes->all(); + $arguments = array(); + foreach ($parameters as $param) { + if (array_key_exists($param->name, $attributes)) { + if (PHP_VERSION_ID >= 50600 && $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(); + } 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..f7b19ed1 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.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\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 + */ + public function getArguments(Request $request, $controller); +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php new file mode 100644 index 00000000..f8de31cf --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.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\Controller; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpFoundation\Request; + +/** + * TraceableControllerResolver. + * + * @author Fabien Potencier + */ +class TraceableControllerResolver implements ControllerResolverInterface +{ + private $resolver; + private $stopwatch; + + /** + * Constructor. + * + * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + */ + public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function getController(Request $request) + { + $e = $this->stopwatch->start('controller.get_callable'); + + $ret = $this->resolver->getController($request); + + $e->stop(); + + return $ret; + } + + /** + * {@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/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..395fee39 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php @@ -0,0 +1,291 @@ + + * + * 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; + +/** + * ConfigDataCollector. + * + * @author Fabien Potencier + */ +class ConfigDataCollector extends DataCollector +{ + /** + * @var KernelInterface + */ + private $kernel; + private $name; + private $version; + private $cacheVersionInfo = true; + + /** + * 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; + } + + /** + * 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, + 'xdebug_enabled' => extension_loaded('xdebug'), + 'eaccel_enabled' => extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'), + 'apc_enabled' => extension_loaded('apc') && ini_get('apc.enabled'), + 'xcache_enabled' => extension_loaded('xcache') && ini_get('xcache.cacher'), + 'wincache_enabled' => extension_loaded('wincache') && ini_get('wincache.ocenabled'), + '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] = $bundle->getPath(); + } + + $this->data['symfony_state'] = $this->determineSymfonyState(); + } + } + + 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']; + } + + public function setCacheVersionInfo($cacheVersionInfo) + { + $this->cacheVersionInfo = $cacheVersionInfo; + } + + /** + * Gets the PHP version. + * + * @return string The PHP version + */ + public function getPhpVersion() + { + return $this->data['php_version']; + } + + /** + * 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 EAccelerator is enabled. + * + * @return bool true if EAccelerator is enabled, false otherwise + */ + public function hasEAccelerator() + { + return $this->data['eaccel_enabled']; + } + + /** + * Returns true if APC is enabled. + * + * @return bool true if APC is enabled, false otherwise + */ + public function hasApc() + { + return $this->data['apc_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']; + } + + /** + * Returns true if XCache is enabled. + * + * @return bool true if XCache is enabled, false otherwise + */ + public function hasXCache() + { + return $this->data['xcache_enabled']; + } + + /** + * Returns true if WinCache is enabled. + * + * @return bool true if WinCache is enabled, false otherwise + */ + public function hasWinCache() + { + return $this->data['wincache_enabled']; + } + + /** + * Returns true if any accelerator is enabled. + * + * @return bool true if any accelerator is enabled, false otherwise + */ + public function hasAccelerator() + { + return $this->hasApc() || $this->hasZendOpcache() || $this->hasEAccelerator() || $this->hasXCache() || $this->hasWinCache(); + } + + 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..5dca6529 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollector.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\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; + +/** + * 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; + + public function serialize() + { + return serialize($this->data); + } + + public function unserialize($data) + { + $this->data = unserialize($data); + } + + /** + * Converts a PHP variable to a string. + * + * @param mixed $var A PHP variable + * + * @return string The string representation of the variable + */ + protected function varToString($var) + { + 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..ab52678c --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.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\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; + +/** + * @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 \Twig_Template) { + $info = $trace[$i]['object']; + $name = $info->getTemplateName(); + $src = method_exists($info, 'getSource') ? $info->getSource() : $info->getEnvironment()->getLoader()->getSource($name); + $info = $info->getDebugInfo(); + if (null !== $src && isset($info[$trace[$i - 1]['line']])) { + $file = false; + $line = $info[$trace[$i - 1]['line']]; + $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]).'